@typescript-eslint/typescript-estree
Version:
A parser that converts TypeScript source code into an ESTree compatible form
290 lines • 13.8 kB
JavaScript
;
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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.clearProgramCache = clearProgramCache;
exports.clearDefaultProjectMatchedFiles = clearDefaultProjectMatchedFiles;
exports.parse = parse;
exports.clearParseAndGenerateServicesCalls = clearParseAndGenerateServicesCalls;
exports.parseAndGenerateServices = parseAndGenerateServices;
const debug_1 = __importDefault(require("debug"));
const ts = __importStar(require("typescript"));
const ast_converter_1 = require("./ast-converter");
const convert_1 = require("./convert");
const createIsolatedProgram_1 = require("./create-program/createIsolatedProgram");
const createProjectProgram_1 = require("./create-program/createProjectProgram");
const createSourceFile_1 = require("./create-program/createSourceFile");
const getWatchProgramsForProjects_1 = require("./create-program/getWatchProgramsForProjects");
const shared_1 = require("./create-program/shared");
const useProvidedPrograms_1 = require("./create-program/useProvidedPrograms");
const createParserServices_1 = require("./createParserServices");
const createParseSettings_1 = require("./parseSettings/createParseSettings");
const getProjectConfigFiles_1 = require("./parseSettings/getProjectConfigFiles");
const semantic_or_syntactic_errors_1 = require("./semantic-or-syntactic-errors");
const useProgramFromProjectService_1 = require("./useProgramFromProjectService");
const log = (0, debug_1.default)('typescript-eslint:typescript-estree:parser');
/**
* Cache existing programs for the single run use-case.
*
* clearProgramCache() is only intended to be used in testing to ensure the parser is clean between tests.
*/
const existingPrograms = new Map();
/**
* Tsconfigs with project references, which single runs leave to the project service.
*/
const configFilesWithReferences = new Set();
/**
* Directories' config files as the project service finds them, which unlike
* `parserOptions.project: true` includes jsconfig.json files.
*/
const projectServiceConfigFileMatches = new Map();
function clearProgramCache() {
existingPrograms.clear();
configFilesWithReferences.clear();
projectServiceConfigFileMatches.clear();
}
/**
* Files don't change on disk during a single run, so the project service's
* bookkeeping for each opened file isn't needed. Instead, each file can come
* from a Program created once for its tsconfig, the same as with
* `parserOptions.project`. Anything less straightforward is left to the
* project service: default project files, extra file extensions, project
* references, and files not found in their tsconfig's Program.
*/
function useSingleRunProgramForProjectService(parseSettings) {
if (parseSettings.extraFileExtensions.length ||
parseSettings.projectService?.allowDefaultProject?.length) {
return undefined;
}
let configFile;
try {
[configFile] =
(0, getProjectConfigFiles_1.getProjectConfigFiles)({
...parseSettings,
tsconfigMatchCache: projectServiceConfigFileMatches,
}, true, ['tsconfig.json', 'jsconfig.json']) ?? [];
}
catch {
return undefined;
}
if (!configFile) {
return undefined;
}
const canonicalConfigFile = (0, shared_1.getCanonicalFileName)(configFile);
if (configFilesWithReferences.has(canonicalConfigFile)) {
return undefined;
}
let program = existingPrograms.get(canonicalConfigFile);
if (!program) {
// The project service loads referenced projects from their source files,
// while a Program would use their built declaration files.
// References aren't inherited through extends, so the raw file is enough.
const rawConfig = ts.readConfigFile(configFile, ts.sys.readFile).config;
if (rawConfig?.references?.length) {
configFilesWithReferences.add(canonicalConfigFile);
return undefined;
}
log('Creating single-run Program for project service: %s', configFile);
program = (0, useProvidedPrograms_1.createProgramFromConfigFile)(configFile, undefined, parseSettings.jsDocParsingMode);
existingPrograms.set(canonicalConfigFile, program);
}
const astAndProgram = (0, shared_1.getAstFromProgram)(program, parseSettings.filePath);
if (astAndProgram?.ast.text !== parseSettings.codeFullText) {
return undefined;
}
astAndProgram.program.getTypeChecker(); // ensure parent pointers are set in source files
return astAndProgram;
}
const defaultProjectMatchedFiles = new Set();
function clearDefaultProjectMatchedFiles() {
defaultProjectMatchedFiles.clear();
}
/**
* @param parseSettings Internal settings for parsing the file
* @param hasFullTypeInformation True if the program should be attempted to be calculated from provided tsconfig files
* @returns Returns a source file and program corresponding to the linted code
*/
function getProgramAndAST(parseSettings, hasFullTypeInformation) {
if (parseSettings.projectService) {
if (parseSettings.singleRun && hasFullTypeInformation) {
const fromSingleRunProgram = useSingleRunProgramForProjectService(parseSettings);
if (fromSingleRunProgram) {
return fromSingleRunProgram;
}
}
const fromProjectService = (0, useProgramFromProjectService_1.useProgramFromProjectService)(parseSettings.projectService, parseSettings, hasFullTypeInformation, defaultProjectMatchedFiles);
if (fromProjectService) {
return fromProjectService;
}
}
if (parseSettings.programs) {
return (0, useProvidedPrograms_1.useProvidedPrograms)(parseSettings.programs, parseSettings);
}
// no need to waste time creating a program as the caller didn't want parser services
// so we can save time and just create a lonesome source file
if (!hasFullTypeInformation) {
return (0, createSourceFile_1.createNoProgram)(parseSettings);
}
return (0, createProjectProgram_1.createProjectProgram)(parseSettings, (0, getWatchProgramsForProjects_1.getWatchProgramsForProjects)(parseSettings));
}
function parse(code, options) {
const { ast } = parseWithNodeMapsInternal(code, options, false);
return ast;
}
function parseWithNodeMapsInternal(code, options, shouldPreserveNodeMaps) {
/**
* Reset the parse configuration
*/
const parseSettings = (0, createParseSettings_1.createParseSettings)(code, options);
/**
* Ensure users do not attempt to use parse() when they need parseAndGenerateServices()
*/
if (options?.errorOnTypeScriptSyntacticAndSemanticIssues) {
throw new Error(`"errorOnTypeScriptSyntacticAndSemanticIssues" is only supported for parseAndGenerateServices()`);
}
/**
* Create a ts.SourceFile directly, no ts.Program is needed for a simple parse
*/
const ast = (0, createSourceFile_1.createSourceFile)(parseSettings);
/**
* Convert the TypeScript AST to an ESTree-compatible one
*/
const { astMaps, estree } = (0, ast_converter_1.astConverter)(ast, parseSettings, shouldPreserveNodeMaps);
return {
ast: estree,
esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap,
tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap,
};
}
let parseAndGenerateServicesCalls = {};
// Privately exported utility intended for use in typescript-eslint unit tests only
function clearParseAndGenerateServicesCalls() {
parseAndGenerateServicesCalls = {};
}
function parseAndGenerateServices(code, tsestreeOptions) {
/**
* Reset the parse configuration
*/
const parseSettings = (0, createParseSettings_1.createParseSettings)(code, tsestreeOptions);
/**
* If this is a single run in which the user has not provided any existing programs but there
* are programs which need to be created from the provided "project" option,
* create an Iterable which will lazily create the programs as needed by the iteration logic
*/
if (parseSettings.singleRun &&
!parseSettings.programs &&
parseSettings.projects.size > 0) {
parseSettings.programs = {
*[Symbol.iterator]() {
for (const configFile of parseSettings.projects) {
const existingProgram = existingPrograms.get(configFile[0]);
if (existingProgram) {
yield existingProgram;
}
else {
log('Detected single-run/CLI usage, creating Program once ahead of time for project: %s', configFile);
const newProgram = (0, useProvidedPrograms_1.createProgramFromConfigFile)(configFile[1]);
existingPrograms.set(configFile[0], newProgram);
yield newProgram;
}
}
},
};
}
const hasFullTypeInformation = parseSettings.programs != null ||
parseSettings.projects.size > 0 ||
!!parseSettings.projectService;
if (typeof tsestreeOptions.errorOnTypeScriptSyntacticAndSemanticIssues ===
'boolean' &&
tsestreeOptions.errorOnTypeScriptSyntacticAndSemanticIssues) {
parseSettings.errorOnTypeScriptSyntacticAndSemanticIssues = true;
}
if (parseSettings.errorOnTypeScriptSyntacticAndSemanticIssues &&
!hasFullTypeInformation) {
throw new Error('Cannot calculate TypeScript semantic issues without a valid project.');
}
/**
* If we are in singleRun mode but the parseAndGenerateServices() function has been called more than once for the current file,
* it must mean that we are in the middle of an ESLint automated fix cycle (in which parsing can be performed up to an additional
* 10 times in order to apply all possible fixes for the file).
*
* In this scenario we cannot rely upon the singleRun AOT compiled programs because the SourceFiles will not contain the source
* with the latest fixes applied. Therefore we fallback to creating the quickest possible isolated program from the updated source.
*
* Note: This fallback is only needed for the legacy `project` option which uses AOT compiled programs.
* When `projectService` is used, the TypeScript language service always provides up-to-date programs,
* so no fallback is necessary. Additionally, external parsers like vue-eslint-parser may call
* parseAndGenerateServices() multiple times for the same file in a single lint pass, which would
* incorrectly trigger this fallback.
*/
if (parseSettings.singleRun && tsestreeOptions.filePath) {
parseAndGenerateServicesCalls[tsestreeOptions.filePath] =
(parseAndGenerateServicesCalls[tsestreeOptions.filePath] || 0) + 1;
}
const { ast, program } = parseSettings.singleRun &&
tsestreeOptions.filePath &&
parseAndGenerateServicesCalls[tsestreeOptions.filePath] > 1 &&
!parseSettings.projectService
? (0, createIsolatedProgram_1.createIsolatedProgram)(parseSettings)
: getProgramAndAST(parseSettings, hasFullTypeInformation);
/**
* Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve
* mappings between converted and original AST nodes
*/
const shouldPreserveNodeMaps = typeof parseSettings.preserveNodeMaps === 'boolean'
? parseSettings.preserveNodeMaps
: true;
const { astMaps, estree } = (0, ast_converter_1.astConverter)(ast, parseSettings, shouldPreserveNodeMaps);
/**
* Even if TypeScript parsed the source code ok, and we had no problems converting the AST,
* there may be other syntactic or semantic issues in the code that we can optionally report on.
*/
if (program && parseSettings.errorOnTypeScriptSyntacticAndSemanticIssues) {
const error = (0, semantic_or_syntactic_errors_1.getFirstSemanticOrSyntacticError)(program, ast);
if (error) {
throw (0, convert_1.convertError)(error);
}
}
/**
* Return the converted AST and additional parser services
*/
return {
ast: estree,
services: (0, createParserServices_1.createParserServices)(astMaps, program),
};
}
//# sourceMappingURL=parser.js.map