UNPKG

@adpt/core

Version:
357 lines 14.5 kB
"use strict"; 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