| "use strict"; |
| var __importDefault = (this && this.__importDefault) || function (mod) { |
| return (mod && mod.__esModule) ? mod : { "default": mod }; |
| }; |
| var __importStar = (this && this.__importStar) || function (mod) { |
| if (mod && mod.__esModule) return mod; |
| var result = {}; |
| if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; |
| result["default"] = mod; |
| return result; |
| }; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| const path_1 = __importDefault(require("path")); |
| const ts = __importStar(require("typescript")); // leave this as * as ts so people using util package don't need syntheticDefaultImports |
| //------------------------------------------------------------------------------ |
| // Environment calculation |
| //------------------------------------------------------------------------------ |
| /** |
| * Default compiler options for program generation from single root file |
| */ |
| const defaultCompilerOptions = { |
| allowNonTsExtensions: true, |
| allowJs: true, |
| }; |
| /** |
| * Maps tsconfig paths to their corresponding file contents and resulting watches |
| */ |
| const knownWatchProgramMap = new Map(); |
| /** |
| * Maps file paths to their set of corresponding watch callbacks |
| * There may be more than one per file if a file is shared between projects |
| */ |
| const watchCallbackTrackingMap = new Map(); |
| const parsedFilesSeen = new Set(); |
| /** |
| * Holds information about the file currently being linted |
| */ |
| const currentLintOperationState = { |
| code: '', |
| filePath: '', |
| }; |
| /** |
| * Appropriately report issues found when reading a config file |
| * @param diagnostic The diagnostic raised when creating a program |
| */ |
| function diagnosticReporter(diagnostic) { |
| throw new Error(ts.flattenDiagnosticMessageText(diagnostic.messageText, ts.sys.newLine)); |
| } |
| const noopFileWatcher = { close: () => { } }; |
| function getTsconfigPath(tsconfigPath, extra) { |
| return path_1.default.isAbsolute(tsconfigPath) |
| ? tsconfigPath |
| : path_1.default.join(extra.tsconfigRootDir || process.cwd(), tsconfigPath); |
| } |
| /** |
| * Calculate project environments using options provided by consumer and paths from config |
| * @param code The code being linted |
| * @param filePath The path of the file being parsed |
| * @param extra.tsconfigRootDir The root directory for relative tsconfig paths |
| * @param extra.project Provided tsconfig paths |
| * @returns The programs corresponding to the supplied tsconfig paths |
| */ |
| function calculateProjectParserOptions(code, filePath, extra) { |
| const results = []; |
| // preserve reference to code and file being linted |
| currentLintOperationState.code = code; |
| currentLintOperationState.filePath = filePath; |
| // Update file version if necessary |
| // TODO: only update when necessary, currently marks as changed on every lint |
| const watchCallback = watchCallbackTrackingMap.get(filePath); |
| if (parsedFilesSeen.has(filePath) && typeof watchCallback !== 'undefined') { |
| watchCallback(filePath, ts.FileWatcherEventKind.Changed); |
| } |
| for (const rawTsconfigPath of extra.projects) { |
| const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); |
| const existingWatch = knownWatchProgramMap.get(tsconfigPath); |
| if (typeof existingWatch !== 'undefined') { |
| // get new program (updated if necessary) |
| const updatedProgram = existingWatch.getProgram().getProgram(); |
| updatedProgram.getTypeChecker(); // sets parent pointers in source files |
| results.push(updatedProgram); |
| continue; |
| } |
| // create compiler host |
| const watchCompilerHost = ts.createWatchCompilerHost(tsconfigPath, |
| /*optionsToExtend*/ { allowNonTsExtensions: true }, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, diagnosticReporter, |
| /*reportWatchStatus*/ () => { }); |
| // ensure readFile reads the code being linted instead of the copy on disk |
| const oldReadFile = watchCompilerHost.readFile; |
| watchCompilerHost.readFile = (filePath, encoding) => path_1.default.normalize(filePath) === |
| path_1.default.normalize(currentLintOperationState.filePath) |
| ? currentLintOperationState.code |
| : oldReadFile(filePath, encoding); |
| // ensure process reports error on failure instead of exiting process immediately |
| watchCompilerHost.onUnRecoverableConfigFileDiagnostic = diagnosticReporter; |
| // ensure process doesn't emit programs |
| watchCompilerHost.afterProgramCreate = program => { |
| // report error if there are any errors in the config file |
| const configFileDiagnostics = program |
| .getConfigFileParsingDiagnostics() |
| .filter(diag => diag.category === ts.DiagnosticCategory.Error && |
| diag.code !== 18003); |
| if (configFileDiagnostics.length > 0) { |
| diagnosticReporter(configFileDiagnostics[0]); |
| } |
| }; |
| // register callbacks to trigger program updates without using fileWatchers |
| watchCompilerHost.watchFile = (fileName, callback) => { |
| const normalizedFileName = path_1.default.normalize(fileName); |
| watchCallbackTrackingMap.set(normalizedFileName, callback); |
| return { |
| close: () => { |
| watchCallbackTrackingMap.delete(normalizedFileName); |
| }, |
| }; |
| }; |
| // ensure fileWatchers aren't created for directories |
| watchCompilerHost.watchDirectory = () => noopFileWatcher; |
| // allow files with custom extensions to be included in program (uses internal ts api) |
| const oldOnDirectoryStructureHostCreate = watchCompilerHost |
| .onCachedDirectoryStructureHostCreate; |
| watchCompilerHost.onCachedDirectoryStructureHostCreate = (host) => { |
| const oldReadDirectory = host.readDirectory; |
| host.readDirectory = (path, extensions, exclude, include, depth) => oldReadDirectory(path, !extensions |
| ? undefined |
| : extensions.concat(extra.extraFileExtensions), exclude, include, depth); |
| oldOnDirectoryStructureHostCreate(host); |
| }; |
| // create program |
| const programWatch = ts.createWatchProgram(watchCompilerHost); |
| const program = programWatch.getProgram().getProgram(); |
| // cache watch program and return current program |
| knownWatchProgramMap.set(tsconfigPath, programWatch); |
| results.push(program); |
| } |
| parsedFilesSeen.add(filePath); |
| return results; |
| } |
| exports.calculateProjectParserOptions = calculateProjectParserOptions; |
| /** |
| * Create program from single root file. Requires a single tsconfig to be specified. |
| * @param code The code being linted |
| * @param filePath The file being linted |
| * @param extra.tsconfigRootDir The root directory for relative tsconfig paths |
| * @param extra.project Provided tsconfig paths |
| * @returns The program containing just the file being linted and associated library files |
| */ |
| function createProgram(code, filePath, extra) { |
| if (!extra.projects || extra.projects.length !== 1) { |
| return undefined; |
| } |
| const tsconfigPath = getTsconfigPath(extra.projects[0], extra); |
| const commandLine = ts.getParsedCommandLineOfConfigFile(tsconfigPath, defaultCompilerOptions, Object.assign({}, ts.sys, { onUnRecoverableConfigFileDiagnostic: () => { } })); |
| if (!commandLine) { |
| return undefined; |
| } |
| const compilerHost = ts.createCompilerHost(commandLine.options, true); |
| const oldReadFile = compilerHost.readFile; |
| compilerHost.readFile = (fileName) => path_1.default.normalize(fileName) === path_1.default.normalize(filePath) |
| ? code |
| : oldReadFile(fileName); |
| return ts.createProgram([filePath], commandLine.options, compilerHost); |
| } |
| exports.createProgram = createProgram; |
| //# sourceMappingURL=tsconfig-parser.js.map |