"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CompilerState = void 0; const path = __importStar(require("path")); const ts = __importStar(require("typescript")); const colors = require("colors"); const node_core_library_1 = require("@rushstack/node-core-library"); const ExtractorConfig_1 = require("./ExtractorConfig"); /** * This class represents the TypeScript compiler state. This allows an optimization where multiple invocations * of API Extractor can reuse the same TypeScript compiler analysis. * * @public */ class CompilerState { constructor(properties) { this.program = properties.program; } /** * Create a compiler state for use with the specified `IExtractorInvokeOptions`. */ static create(extractorConfig, options) { let tsconfig = extractorConfig.overrideTsconfig; let configBasePath = extractorConfig.projectFolder; if (!tsconfig) { // If it wasn't overridden, then load it from disk tsconfig = node_core_library_1.JsonFile.load(extractorConfig.tsconfigFilePath); configBasePath = path.resolve(path.dirname(extractorConfig.tsconfigFilePath)); } const commandLine = ts.parseJsonConfigFileContent(tsconfig, ts.sys, configBasePath); if (!commandLine.options.skipLibCheck && extractorConfig.skipLibCheck) { commandLine.options.skipLibCheck = true; console.log(colors.cyan('API Extractor was invoked with skipLibCheck. This is not recommended and may cause ' + 'incorrect type analysis.')); } const inputFilePaths = commandLine.fileNames.concat(extractorConfig.mainEntryPointFilePath); if (options && options.additionalEntryPoints) { inputFilePaths.push(...options.additionalEntryPoints); } // Append the entry points and remove any non-declaration files from the list const analysisFilePaths = CompilerState._generateFilePathsForAnalysis(inputFilePaths); const compilerHost = CompilerState._createCompilerHost(commandLine, options); const program = ts.createProgram(analysisFilePaths, commandLine.options, compilerHost); if (commandLine.errors.length > 0) { const errorText = ts.flattenDiagnosticMessageText(commandLine.errors[0].messageText, '\n'); throw new Error(`Error parsing tsconfig.json content: ${errorText}`); } return new CompilerState({ program }); } /** * Given a list of absolute file paths, return a list containing only the declaration * files. Duplicates are also eliminated. * * @remarks * The tsconfig.json settings specify the compiler's input (a set of *.ts source files, * plus some *.d.ts declaration files used for legacy typings). However API Extractor * analyzes the compiler's output (a set of *.d.ts entry point files, plus any legacy * typings). This requires API Extractor to generate a special file list when it invokes * the compiler. * * Duplicates are removed so that entry points can be appended without worrying whether they * may already appear in the tsconfig.json file list. */ static _generateFilePathsForAnalysis(inputFilePaths) { const analysisFilePaths = []; const seenFiles = new Set(); for (const inputFilePath of inputFilePaths) { const inputFileToUpper = inputFilePath.toUpperCase(); if (!seenFiles.has(inputFileToUpper)) { seenFiles.add(inputFileToUpper); if (!path.isAbsolute(inputFilePath)) { throw new Error('Input file is not an absolute path: ' + inputFilePath); } if (ExtractorConfig_1.ExtractorConfig.hasDtsFileExtension(inputFilePath)) { analysisFilePaths.push(inputFilePath); } } } return analysisFilePaths; } static _createCompilerHost(commandLine, options) { // Create a default CompilerHost that we will override const compilerHost = ts.createCompilerHost(commandLine.options); // Save a copy of the original members. Note that "compilerHost" cannot be the copy, because // createCompilerHost() captures that instance in a closure that is used by the members. const defaultCompilerHost = Object.assign({}, compilerHost); if (options && options.typescriptCompilerFolder) { // Prevent a closure parameter const typescriptCompilerLibFolder = path.join(options.typescriptCompilerFolder, 'lib'); compilerHost.getDefaultLibLocation = () => typescriptCompilerLibFolder; } // Used by compilerHost.fileExists() // .d.ts file path --> whether the file exists const dtsExistsCache = new Map(); // Used by compilerHost.fileExists() // Example: "c:/folder/file.part.ts" const fileExtensionRegExp = /^(.+)(\.[a-z0-9_]+)$/i; compilerHost.fileExists = (fileName) => { // In certain deprecated setups, the compiler may write its output files (.js and .d.ts) // in the same folder as the corresponding input file (.ts or .tsx). When following imports, // API Extractor wants to analyze the .d.ts file; however recent versions of the compiler engine // will instead choose the .ts file. To work around this, we hook fileExists() to hide the // existence of those files. // Is "fileName" a .d.ts file? The double extension ".d.ts" needs to be matched specially. if (!ExtractorConfig_1.ExtractorConfig.hasDtsFileExtension(fileName)) { // It's not a .d.ts file. Is the file extension a potential source file? const match = fileExtensionRegExp.exec(fileName); if (match) { // Example: "c:/folder/file.part" const pathWithoutExtension = match[1]; // Example: ".ts" const fileExtension = match[2]; switch (fileExtension.toLocaleLowerCase()) { case '.ts': case '.tsx': case '.js': case '.jsx': // Yes, this is a possible source file. Is there a corresponding .d.ts file in the same folder? const dtsFileName = `${pathWithoutExtension}.d.ts`; let dtsFileExists = dtsExistsCache.get(dtsFileName); if (dtsFileExists === undefined) { dtsFileExists = defaultCompilerHost.fileExists(dtsFileName); dtsExistsCache.set(dtsFileName, dtsFileExists); } if (dtsFileExists) { // fileName is a potential source file and a corresponding .d.ts file exists. // Thus, API Extractor should ignore this file (so the .d.ts file will get analyzed instead). return false; } break; } } } // Fall through to the default implementation return defaultCompilerHost.fileExists(fileName); }; return compilerHost; } } exports.CompilerState = CompilerState; //# sourceMappingURL=CompilerState.js.map