Last active
June 1, 2020 13:08
-
-
Save mihailik/11369fd2b5e0603a14bc5d883d47dd6c to your computer and use it in GitHub Desktop.
TypeScript custom transformers with ts.createSolutionBuilderWithWatch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @ts-check | |
/// <reference types="node" /> | |
var ts = require('typescript'); // little change | |
var tsconfig_json = JSON.stringify({ | |
compilerOptions: { | |
outFile: __filename + '.out.js', | |
allowJs: true, | |
checkJs: true, | |
target: 'es3' | |
}, | |
files: [__filename] | |
}, null, 2); | |
var s = { | |
delete: 3 | |
}; | |
/** @type {import('typescript').System} */ | |
var sysOverride = {}; | |
for (var k in ts.sys) { sysOverride[k] = ts.sys[k]; } | |
sysOverride.readFile = function (file) { | |
if (ts.sys.resolvePath(file) === ts.sys.resolvePath(__dirname + '/tsconfig.json')) { | |
// console.log('readFile(', file, ') -> overridden tsconfig_json'); | |
return tsconfig_json; | |
} | |
else { | |
var result = ts.sys.readFile(file); | |
// if (!/node_modules/.test(file)) | |
// console.log('readFile(', file, ') -> ' + (typeof result === 'string' ? '"' + result.length + '"' : typeof result)); | |
return result; | |
} | |
}; | |
sysOverride.writeFile = function (file, content) { | |
console.log(' sys.writeFile(', file, ', [', content.length, '])'); | |
ts.sys.writeFile(file, content); | |
}; | |
var host = ts.createSolutionBuilderWithWatchHost( | |
sysOverride, | |
void 0, | |
reportDiag, | |
reportDiag, | |
reportWatch); | |
var buildStart = Date.now(); | |
var solutionBuilder = ts.createSolutionBuilderWithWatch( | |
host, | |
[__dirname], | |
{ incremental: false }, {}); | |
initiateFirstBuild(); | |
function initiateFirstBuild() { | |
var firstBuild = solutionBuilder.getNextInvalidatedProject(); | |
if (firstBuild) { | |
buildStart = Date.now(); | |
startBuild(firstBuild); | |
} | |
solutionBuilder.build(); | |
} | |
/** | |
* @param {import('typescript').InvalidatedProject<import('typescript').EmitAndSemanticDiagnosticsBuilderProgram>} proj | |
* @param {import('typescript').Diagnostic=} watchDiag | |
*/ | |
function startBuild(proj, watchDiag) { | |
ts.sys.write( | |
'\x1b[93m ' + (ts.InvalidatedProjectKind[proj.kind] + ' ').slice(0, 10) + '\x1b[0m' + | |
(watchDiag ? '' : '\n')); | |
if (watchDiag) reportDiag(watchDiag); | |
buildStart = Date.now(); | |
if (proj && proj.kind === ts.InvalidatedProjectKind.Build) { | |
progSource = proj; | |
proj.emit( | |
void 0, | |
void 0, | |
void 0, | |
void 0, | |
{ after: [transformInjectStatementNumbers] }); | |
} | |
} | |
function completeBuild(watchDiag) { | |
ts.sys.write('\x1b[90m ' + (((Date.now() - buildStart) / 1000) + 's ').slice(0, 10) + '\x1b[0m'); | |
if (watchDiag) reportDiag(watchDiag); | |
} | |
/** @type {import('typescript').FormatDiagnosticsHost} */ | |
var diagHost; | |
/** @param {import('typescript').Diagnostic} diag */ | |
function reportDiag(diag) { | |
if (!diagHost) { | |
diagHost = { | |
getCanonicalFileName: function (fileName) { | |
return ts.sys.resolvePath(fileName) | |
}, | |
getCurrentDirectory: function () { | |
return ts.sys.getCurrentDirectory(); | |
}, | |
getNewLine: function () { | |
return ts.sys.newLine; | |
} | |
}; | |
} | |
var output = ts.sys.writeOutputIsTTY && ts.sys.writeOutputIsTTY() ? | |
ts.formatDiagnosticsWithColorAndContext([diag], diagHost) : | |
ts.formatDiagnostic(diag, diagHost); | |
output = output.replace(/^[\r\n]+/, '').replace(/[\r\n]+$/, ''); | |
ts.sys.write(output + '\n'); | |
} | |
/** @param {import('typescript').Diagnostic} diag */ | |
function reportWatch(diag) { | |
var proj = solutionBuilder.getNextInvalidatedProject(); | |
if (proj && /** @type {*} */(proj).getProgram) { | |
progSource = /** @type {*} */(proj); | |
} | |
if (proj) | |
startBuild(proj, diag); | |
else | |
completeBuild(diag); | |
} | |
/** @type {{ getProgram(): import('typescript').Program }} */ | |
var progSource; | |
/** @type {import('typescript').TypeChecker} */ | |
var checker; | |
/** @param {import('typescript').TransformationContext} context */ | |
function transformInjectStatementNumbers(context) { | |
checker = progSource.getProgram().getTypeChecker(); | |
return transformFile; | |
function transformFile(sourceFile) { | |
console.log(' transforming(', sourceFile.fileName, ')...'); | |
return ts.updateSourceFileNode( | |
sourceFile, | |
sourceFile.statements.map(decorateStatementWithComplexityAndType)); | |
} | |
} | |
/** | |
* @param {import('typescript').Statement} statement | |
*/ | |
function decorateStatementWithComplexityAndType(statement) { | |
var nodeCount = 0; | |
var type; | |
ts.forEachChild(statement, visitStatementChild); | |
return ts.addSyntheticLeadingComment( | |
statement, ts.SyntaxKind.SingleLineCommentTrivia, | |
' INJECTED >> complexity: ' + nodeCount + | |
(!type ? '' : ' : ' + checker.typeToString(type))); | |
/** | |
* @param {import('typescript').Node} child | |
*/ | |
function visitStatementChild(child) { | |
nodeCount++; | |
if (!type) type = checker.getTypeAtLocation(child); | |
if (type.getFlags() === ts.TypeFlags.Any) type = null; | |
ts.forEachChild(child, visitStatementChild); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment