109 lines
3.5 KiB
JavaScript
109 lines
3.5 KiB
JavaScript
const path = require('path')
|
|
const fs = require('fs')
|
|
|
|
const ts = require('typescript')
|
|
const JSON5 = require('json5')
|
|
|
|
const Builder = require('./Builder.cjs')
|
|
|
|
const rootDir = path.join(__dirname, '../../../../')
|
|
const mochaTsRelativeDir = '.mocha-ts'
|
|
const mochaTsDir = path.join(rootDir, mochaTsRelativeDir)
|
|
|
|
const formatHost = {
|
|
getCanonicalFileName: path => path,
|
|
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
|
getNewLine: () => ts.sys.newLine
|
|
}
|
|
|
|
module.exports = class TestsBuilder extends Builder {
|
|
constructor ({ name = 'tsc', configPath = path.join(rootDir, 'tsconfig.json'), tempDir = mochaTsDir }) {
|
|
super(path.join(tempDir, 'semaphore'), name)
|
|
|
|
if (fs.existsSync(configPath) !== true) throw new Error(`Couldn't find a tsconfig file at ${configPath}`)
|
|
|
|
this.tempDir = tempDir
|
|
|
|
const tsConfig = JSON5.parse(fs.readFileSync(configPath, 'utf8'))
|
|
|
|
tsConfig.file = undefined
|
|
|
|
// Exclude already transpiled files in src
|
|
tsConfig.exclude = ['src/ts/**/!(*.spec).ts']
|
|
|
|
// "noResolve": true
|
|
tsConfig.compilerOptions.noResolve = false
|
|
|
|
// we don't need declaration files
|
|
tsConfig.compilerOptions.declaration = false
|
|
|
|
// we need to emit files
|
|
tsConfig.compilerOptions.noEmit = false
|
|
|
|
// source mapping eases debuging
|
|
tsConfig.compilerOptions.sourceMap = true
|
|
|
|
// This prevents SyntaxError: Cannot use import statement outside a module
|
|
tsConfig.compilerOptions.module = 'commonjs'
|
|
|
|
// Removed typeroots (it causes issues)
|
|
tsConfig.compilerOptions.typeRoots = undefined
|
|
|
|
tsConfig.compilerOptions.outDir = path.isAbsolute(tempDir) ? path.relative(rootDir, tempDir) : tempDir
|
|
|
|
this.tempTsConfigPath = path.join(rootDir, '.tsconfig.json')
|
|
|
|
fs.writeFileSync(this.tempTsConfigPath, JSON.stringify(tsConfig, undefined, 2))
|
|
|
|
const createProgram = ts.createSemanticDiagnosticsBuilderProgram
|
|
|
|
const reportDiagnostic = (diagnostic) => {
|
|
const filePath = path.relative(rootDir, diagnostic.file.fileName)
|
|
const tranpiledJsPath = `${path.join(tempDir, filePath).slice(0, -3)}.js`
|
|
const errorLine = diagnostic.file.text.slice(0, diagnostic.start).split(/\r\n|\r|\n/).length
|
|
if (fs.existsSync(tranpiledJsPath)) {
|
|
fs.writeFileSync(tranpiledJsPath, '', 'utf8')
|
|
}
|
|
this.emit('error', `[Error ${diagnostic.code}]`, `${filePath}:${errorLine}`, ':', ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine()))
|
|
}
|
|
|
|
const reportWatchStatusChanged = (diagnostic, newLine, options, errorCount) => {
|
|
if (errorCount !== undefined) {
|
|
this.emit('ready')
|
|
} else {
|
|
this.emit('busy')
|
|
if (diagnostic.code === 6031) {
|
|
this.emit('message', 'transpiling your tests...')
|
|
} else if (diagnostic.code === 6032) {
|
|
this.emit('message', 'file changes detected. Transpiling your tests...')
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note that there is another overload for `createWatchCompilerHost` that takes
|
|
// a set of root files.
|
|
this.host = ts.createWatchCompilerHost(
|
|
this.tempTsConfigPath,
|
|
{},
|
|
ts.sys,
|
|
createProgram,
|
|
reportDiagnostic,
|
|
reportWatchStatusChanged
|
|
)
|
|
}
|
|
|
|
async start () {
|
|
await super.start()
|
|
// `createWatchProgram` creates an initial program, watches files, and updates
|
|
// the program over time.
|
|
this.watcher = ts.createWatchProgram(this.host)
|
|
return await this.ready()
|
|
}
|
|
|
|
async close () {
|
|
await super.close()
|
|
this.watcher.close()
|
|
fs.unlinkSync(this.tempTsConfigPath)
|
|
}
|
|
}
|