@adpt/core
Version:
AdaptJS core library
357 lines • 14.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
/*
* Copyright 2017-2019 Unbounded Systems, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const debug_1 = tslib_1.__importDefault(require("debug"));
const fs = tslib_1.__importStar(require("fs"));
const mkdirp = tslib_1.__importStar(require("mkdirp"));
const path = tslib_1.__importStar(require("path"));
const ts_custom_error_1 = require("ts-custom-error");
const trace_1 = require("../utils/trace");
const context_1 = require("./context");
const hosts_1 = require("./hosts");
const modules_1 = require("./modules");
const ts = tslib_1.__importStar(require("./tsmod"));
const tsmod = ts.tsmod;
const debugCompile = false;
const debugPreprocessing = false;
let debugIntermediateDom = false;
if (debugCompile || debugPreprocessing)
debugIntermediateDom = true;
const debugAction = debug_1.default("adapt:compile:action");
function compilerDefaults() {
return {
target: tsmod().ScriptTarget.ES2016,
module: tsmod().ModuleKind.CommonJS,
experimentalDecorators: true,
inlineSourceMap: true,
allowJs: true,
jsx: tsmod().JsxEmit.React,
jsxFactory: "Adapt.createElement",
resolveJsonModule: true,
noErrorTruncation: true,
esModuleInterop: true,
};
}
class CompileError extends ts_custom_error_1.CustomError {
constructor(diags, msg) {
super(msg);
this.diags = diags;
}
}
exports.CompileError = CompileError;
class EmitError extends ts_custom_error_1.CustomError {
constructor(filename, diags, emitSkipped) {
super();
this.filename = filename;
this.diags = diags;
this.emitSkipped = emitSkipped;
}
}
function diagText(diags, cwd, lineOffset) {
return diags.map((d) => {
let msg = "";
let src = "";
if (d.file) {
msg += path.relative(cwd, d.file.fileName);
if (d.start) {
const { line, character } = d.file.getLineAndCharacterOfPosition(d.start);
msg += ` (${line + 1 + lineOffset},${character + 1})`;
const lineStart = d.file.getPositionOfLineAndCharacter(line, 0);
const lineEnd = d.file.getLineEndOfPosition(d.start);
src = "\n" + d.file.getFullText()
.substr(lineStart, lineEnd - lineStart) + "\n";
src += " ".repeat(character) + "^\n";
}
msg += ": ";
}
msg += tsmod().flattenDiagnosticMessageText(d.messageText, "\n");
msg += ` (${d.code})`;
msg += src;
return msg;
}).join("\n");
}
// Caching sourceFile objects in the host causes issues across
// invocations of createProgram (I think). Don't cache them until
// I can spend some time ensuring it won't cause the compiler to crash
// when using the type checker in the visitors.
const cacheSourceFiles = false;
class DomCompileHost extends hosts_1.ChainableHost {
constructor(rootFiles, compilerOptions, chainHost, id, getProjectVersion) {
super(chainHost.cwd);
this.rootFiles = rootFiles;
this.compilerOptions = compilerOptions;
this.getProjectVersion = getProjectVersion;
this.getScriptFileNames = () => this.rootFiles;
this.getScriptVersion = (filename) => this.getFileVersion(filename);
this.getCompilationSettings = () => this.compilerOptions;
this.cache = new Map();
this.setSource(chainHost);
if (id)
this._id = id;
}
getDefaultLibFileName() { return tsmod().getDefaultLibFilePath(this.compilerOptions); }
cacheEntry(fileName) {
const curVer = this.getScriptVersion(fileName);
if (curVer === undefined)
return undefined;
let c = this.cache.get(fileName);
if (c && (c.version === curVer))
return c;
const contents = this.readFile(fileName);
if (contents === undefined)
return undefined;
// Create new cache entry (possibly throwing away old)
c = { version: curVer, contents };
this.cache.set(fileName, c);
return c;
}
getScriptSnapshot(fileName) {
fileName = this.getCanonicalFileName(fileName);
const c = this.cacheEntry(fileName);
if (!c)
return undefined;
if (c.snapshot)
trace_1.trace(hosts_1.debugChainableHosts, `Cached snapshot: ${fileName}`);
if (c.snapshot)
return c.snapshot;
if (!c.contents)
throw new Error(`Unable to create snapshot`);
trace_1.trace(hosts_1.debugChainableHosts, `New snapshot: ${fileName}`);
c.snapshot = tsmod().ScriptSnapshot.fromString(c.contents);
return c.snapshot;
}
getSourceFile(fileName, languageVersion, onError) {
const c = this.cacheEntry(fileName);
if (!c)
return undefined;
trace_1.trace(hosts_1.debugChainableHosts, `getSourceFile: ${fileName}`);
if (c.sourceFile)
return c.sourceFile;
// Does a lower layer have it?
c.sourceFile = this.source.getSourceFile(fileName, languageVersion, onError);
if (c.sourceFile !== undefined)
return c.sourceFile;
trace_1.trace(hosts_1.debugChainableHosts, `New sourceFile: ${fileName}`);
if (!c.contents)
throw new Error(`Unable to create source file`);
const sf = tsmod().createSourceFile(fileName, c.contents, languageVersion, true);
if (cacheSourceFiles)
c.sourceFile = sf;
return sf;
}
// Return a list of the files unique to this layer
dir() {
const ret = [];
for (const filename of this.cache.keys()) {
const c = this.cache.get(filename);
if (!c)
continue;
let info = "";
if (c.sourceFile)
info += "SF";
if (c.snapshot)
info += "SS";
if (c.contents !== undefined)
info += `C [${c.contents.length}]`;
ret.push(`${filename}: ${info}`);
}
return ret;
}
}
tslib_1.__decorate([
trace_1.tracef(hosts_1.debugChainableHosts),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [String]),
tslib_1.__metadata("design:returntype", Object)
], DomCompileHost.prototype, "getScriptSnapshot", null);
tslib_1.__decorate([
trace_1.tracef(hosts_1.debugChainableHosts),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [String, Number, Function]),
tslib_1.__metadata("design:returntype", void 0)
], DomCompileHost.prototype, "getSourceFile", null);
// Additional extensions to try for module resolution
//const extensions = [".dom"];
class Compiler {
constructor(projectRoot, rootFiles, chainHost, compilerOptions) {
this.projectVersion = 0;
const finalOptions = Object.assign({}, compilerDefaults(), compilerOptions);
finalOptions.allowNonTsExtensions = true;
// This stops the compiler from searching parent directories
finalOptions.typeRoots =
[path.join(projectRoot, "node_modules", "@types")];
const verFunc = this.getProjectVersion.bind(this);
this._rootFiles = rootFiles;
this.baseHost = chainHost;
const partialChain = hosts_1.chainHosts(new modules_1.ModuleResolver(finalOptions, undefined, "Prim"), chainHost);
this.primaryChain =
new DomCompileHost(rootFiles, finalOptions, partialChain, "Prim", verFunc);
this.service = tsmod().createLanguageService(this.primaryChain);
}
get host() {
return this.primaryChain;
}
get rootFiles() {
return this._rootFiles;
}
set rootFiles(val) {
this._rootFiles = val;
this.primaryChain.rootFiles = val;
}
getProjectVersion() {
return this.projectVersion.toString();
}
compile(code, filename, module, lineOffset = 0) {
debugAction(`Compile start: ${filename}`);
this.registerExtension(".ts", module);
this.registerExtension(".tsx", module);
/* The typescript compiler checks the project version to decide
* whether to re-query EVERYTHING, which is both slow and difficult
* to trace issues when debugging because of all the extraneous
* re-computing that happens.
* This module already does not guarantee a consistent snapshot of
* files since the chainable hosts could include one that accesses the
* filesystem. So simply delay the re-check of file versions until
* we're done with a complete invocation of the public compile()
* function. This could be modified to query the host chains on
* whether they know if their files have changed.
*/
this.projectVersion++;
try {
const ret = this._compile(code, filename);
debugAction(`Compile done: ${filename}`);
return ret;
}
catch (err) {
debugAction(`Compile error: ${filename}`);
if (err instanceof EmitError) {
throw new CompileError(err.diags, diagText(err.diags, this.primaryChain.getCurrentDirectory(), lineOffset));
}
throw err;
}
}
registerExtension(ext, mainModule) {
// tslint:disable-next-line:no-this-assignment
const compiler = this;
const chain = this.primaryChain;
// tslint:disable-next-line:only-arrow-functions
mainModule.registerExt(ext, function (mod, filename) {
const oldcompile = mod._compile;
mod._compile = function (code, fname) {
return oldcompile.call(this, compiler._compile(code, fname), fname);
};
try {
return mod._compile(chain.readFile(filename), filename);
}
catch (err) {
if (err instanceof EmitError) {
throw new CompileError(err.diags, diagText(err.diags, compiler.primaryChain.getCurrentDirectory(), 0));
}
throw err;
}
});
}
dir() {
trace_1.trace(hosts_1.debugChainableHosts, `\n*** Primary chain ***`);
this.primaryChain.dirTrace();
}
_compile(_code, filename) {
let output;
const mkDebugWrite = (enable = true) => {
if (!enable)
return () => { return; };
const startPath = this.primaryChain.getCurrentDirectory();
const localOutputDir = path.join(process.cwd(), "DebugOut");
const trim = startPath.length;
return function debugFileWrite(filePath, contents) {
filePath = path.resolve(startPath, filePath);
if (!filePath.startsWith(startPath))
return;
const relPath = filePath.slice(trim);
const outputDir = path.join(localOutputDir, path.dirname(relPath));
try {
fs.statSync(outputDir);
}
catch (err) {
mkdirp.sync(outputDir);
}
fs.writeFileSync(path.join(localOutputDir, relPath), contents);
};
};
try {
debugAction(`Emit output start: ${filename}`);
output = this.service.getEmitOutput(filename);
debugAction(`Emit output done: ${filename}`);
}
catch (err) {
trace_1.trace(hosts_1.debugChainableHosts, `Error compiling. Dumping chain info`);
if (hosts_1.debugChainableHosts)
this.dir();
throw err;
/*
} finally {
if (debugIntermediateDom) {
this.preprocHost._writeFiles(mkDebugWrite());
}
*/
}
const diagnostics = this.service.getCompilerOptionsDiagnostics()
.concat(this.service.getSyntacticDiagnostics(filename))
.concat(this.service.getSemanticDiagnostics(filename));
if (diagnostics.length || output.emitSkipped) {
throw new EmitError(filename, diagnostics, output.emitSkipped);
}
if (/\.d\.ts$/.test(filename)) {
// No output for an input .d.ts file
return null;
}
const debugWrite = mkDebugWrite(debugIntermediateDom);
let jsOut = null;
for (const out of output.outputFiles) {
// Depending on compiler options, there may be .d.ts and .map files
if (/\.js$/.test(out.name)) {
jsOut = out;
}
this.baseHost.writeFile(out.name, out.text);
debugWrite(out.name, out.text);
}
if (!jsOut)
throw new Error(`Couldn't find output file`);
return jsOut.text;
}
}
tslib_1.__decorate([
trace_1.tracef(hosts_1.debugChainableHosts),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", []),
tslib_1.__metadata("design:returntype", String)
], Compiler.prototype, "getProjectVersion", null);
tslib_1.__decorate([
trace_1.tracef(hosts_1.debugChainableHosts),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [String, String, context_1.VmModule, Object]),
tslib_1.__metadata("design:returntype", Object)
], Compiler.prototype, "compile", null);
tslib_1.__decorate([
trace_1.tracef(hosts_1.debugChainableHosts || debugCompile),
tslib_1.__metadata("design:type", Function),
tslib_1.__metadata("design:paramtypes", [String, String]),
tslib_1.__metadata("design:returntype", Object)
], Compiler.prototype, "_compile", null);
exports.Compiler = Compiler;
//# sourceMappingURL=compile.js.map