UNPKG

22 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21var __importDefault = (this && this.__importDefault) || function (mod) {
22 return (mod && mod.__esModule) ? mod : { "default": mod };
23};
24Object.defineProperty(exports, "__esModule", { value: true });
25exports.clearParseAndGenerateServicesCalls = exports.clearProgramCache = exports.parseWithNodeMaps = exports.parseAndGenerateServices = exports.parse = void 0;
26const debug_1 = __importDefault(require("debug"));
27const globby_1 = require("globby");
28const is_glob_1 = __importDefault(require("is-glob"));
29const semver_1 = __importDefault(require("semver"));
30const path_1 = require("path");
31const ts = __importStar(require("typescript"));
32const ast_converter_1 = require("./ast-converter");
33const convert_1 = require("./convert");
34const createDefaultProgram_1 = require("./create-program/createDefaultProgram");
35const createIsolatedProgram_1 = require("./create-program/createIsolatedProgram");
36const createProjectProgram_1 = require("./create-program/createProjectProgram");
37const createSourceFile_1 = require("./create-program/createSourceFile");
38const semantic_or_syntactic_errors_1 = require("./semantic-or-syntactic-errors");
39const shared_1 = require("./create-program/shared");
40const useProvidedPrograms_1 = require("./create-program/useProvidedPrograms");
41const log = (0, debug_1.default)('typescript-eslint:typescript-estree:parser');
42/**
43 * This needs to be kept in sync with the top-level README.md in the
44 * typescript-eslint monorepo
45 */
46const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.3.1 <4.6.0';
47/*
48 * The semver package will ignore prerelease ranges, and we don't want to explicitly document every one
49 * List them all separately here, so we can automatically create the full string
50 */
51const SUPPORTED_PRERELEASE_RANGES = ['4.5.0-beta', '4.5.1-rc'];
52const ACTIVE_TYPESCRIPT_VERSION = ts.version;
53const isRunningSupportedTypeScriptVersion = semver_1.default.satisfies(ACTIVE_TYPESCRIPT_VERSION, [SUPPORTED_TYPESCRIPT_VERSIONS]
54 .concat(SUPPORTED_PRERELEASE_RANGES)
55 .join(' || '));
56let extra;
57let warnedAboutTSVersion = false;
58/**
59 * Cache existing programs for the single run use-case.
60 *
61 * clearProgramCache() is only intended to be used in testing to ensure the parser is clean between tests.
62 */
63const existingPrograms = new Map();
64function clearProgramCache() {
65 existingPrograms.clear();
66}
67exports.clearProgramCache = clearProgramCache;
68function enforceString(code) {
69 /**
70 * Ensure the source code is a string
71 */
72 if (typeof code !== 'string') {
73 return String(code);
74 }
75 return code;
76}
77/**
78 * @param code The code of the file being linted
79 * @param programInstances One or more (potentially lazily constructed) existing programs to use
80 * @param shouldProvideParserServices True if the program should be attempted to be calculated from provided tsconfig files
81 * @param shouldCreateDefaultProgram True if the program should be created from compiler host
82 * @returns Returns a source file and program corresponding to the linted code
83 */
84function getProgramAndAST(code, programInstances, shouldProvideParserServices, shouldCreateDefaultProgram) {
85 return ((programInstances && (0, useProvidedPrograms_1.useProvidedPrograms)(programInstances, extra)) ||
86 (shouldProvideParserServices &&
87 (0, createProjectProgram_1.createProjectProgram)(code, shouldCreateDefaultProgram, extra)) ||
88 (shouldProvideParserServices &&
89 shouldCreateDefaultProgram &&
90 (0, createDefaultProgram_1.createDefaultProgram)(code, extra)) ||
91 (0, createIsolatedProgram_1.createIsolatedProgram)(code, extra));
92}
93/**
94 * Compute the filename based on the parser options.
95 *
96 * Even if jsx option is set in typescript compiler, filename still has to
97 * contain .tsx file extension.
98 *
99 * @param options Parser options
100 */
101function getFileName({ jsx } = {}) {
102 return jsx ? 'estree.tsx' : 'estree.ts';
103}
104/**
105 * Resets the extra config object
106 */
107function resetExtra() {
108 extra = {
109 code: '',
110 comment: false,
111 comments: [],
112 createDefaultProgram: false,
113 debugLevel: new Set(),
114 errorOnTypeScriptSyntacticAndSemanticIssues: false,
115 errorOnUnknownASTType: false,
116 EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false,
117 extraFileExtensions: [],
118 filePath: getFileName(),
119 jsx: false,
120 loc: false,
121 log: console.log,
122 preserveNodeMaps: true,
123 programs: null,
124 projects: [],
125 range: false,
126 strict: false,
127 tokens: null,
128 tsconfigRootDir: process.cwd(),
129 /**
130 * Unless we can reliably infer otherwise, we default to assuming that this run could be part
131 * of a long-running session (e.g. in an IDE) and watch programs will therefore be required
132 */
133 singleRun: false,
134 moduleResolver: '',
135 };
136}
137function getTsconfigPath(tsconfigPath, extra) {
138 return (0, shared_1.getCanonicalFileName)((0, shared_1.ensureAbsolutePath)(tsconfigPath, extra));
139}
140/**
141 * Normalizes, sanitizes, resolves and filters the provided project paths
142 */
143function prepareAndTransformProjects(projectsInput, ignoreListInput) {
144 const sanitizedProjects = [];
145 // Normalize and sanitize the project paths
146 if (typeof projectsInput === 'string') {
147 sanitizedProjects.push(projectsInput);
148 }
149 else if (Array.isArray(projectsInput)) {
150 for (const project of projectsInput) {
151 if (typeof project === 'string') {
152 sanitizedProjects.push(project);
153 }
154 }
155 }
156 if (sanitizedProjects.length === 0) {
157 return [];
158 }
159 // Transform glob patterns into paths
160 const nonGlobProjects = sanitizedProjects.filter(project => !(0, is_glob_1.default)(project));
161 const globProjects = sanitizedProjects.filter(project => (0, is_glob_1.default)(project));
162 const uniqueCanonicalProjectPaths = new Set(nonGlobProjects
163 .concat((0, globby_1.sync)([...globProjects, ...ignoreListInput], {
164 cwd: extra.tsconfigRootDir,
165 }))
166 .map(project => getTsconfigPath(project, extra)));
167 log('parserOptions.project (excluding ignored) matched projects: %s', uniqueCanonicalProjectPaths);
168 return Array.from(uniqueCanonicalProjectPaths);
169}
170function applyParserOptionsToExtra(options) {
171 var _a;
172 /**
173 * Configure Debug logging
174 */
175 if (options.debugLevel === true) {
176 extra.debugLevel = new Set(['typescript-eslint']);
177 }
178 else if (Array.isArray(options.debugLevel)) {
179 extra.debugLevel = new Set(options.debugLevel);
180 }
181 if (extra.debugLevel.size > 0) {
182 // debug doesn't support multiple `enable` calls, so have to do it all at once
183 const namespaces = [];
184 if (extra.debugLevel.has('typescript-eslint')) {
185 namespaces.push('typescript-eslint:*');
186 }
187 if (extra.debugLevel.has('eslint') ||
188 // make sure we don't turn off the eslint debug if it was enabled via --debug
189 debug_1.default.enabled('eslint:*,-eslint:code-path')) {
190 // https://github.com/eslint/eslint/blob/9dfc8501fb1956c90dc11e6377b4cb38a6bea65d/bin/eslint.js#L25
191 namespaces.push('eslint:*,-eslint:code-path');
192 }
193 debug_1.default.enable(namespaces.join(','));
194 }
195 /**
196 * Track range information in the AST
197 */
198 extra.range = typeof options.range === 'boolean' && options.range;
199 extra.loc = typeof options.loc === 'boolean' && options.loc;
200 /**
201 * Track tokens in the AST
202 */
203 if (typeof options.tokens === 'boolean' && options.tokens) {
204 extra.tokens = [];
205 }
206 /**
207 * Track comments in the AST
208 */
209 if (typeof options.comment === 'boolean' && options.comment) {
210 extra.comment = true;
211 extra.comments = [];
212 }
213 /**
214 * Enable JSX - note the applicable file extension is still required
215 */
216 if (typeof options.jsx === 'boolean' && options.jsx) {
217 extra.jsx = true;
218 }
219 /**
220 * Get the file path
221 */
222 if (typeof options.filePath === 'string' && options.filePath !== '<input>') {
223 extra.filePath = options.filePath;
224 }
225 else {
226 extra.filePath = getFileName(extra);
227 }
228 /**
229 * Allow the user to cause the parser to error if it encounters an unknown AST Node Type
230 * (used in testing)
231 */
232 if (typeof options.errorOnUnknownASTType === 'boolean' &&
233 options.errorOnUnknownASTType) {
234 extra.errorOnUnknownASTType = true;
235 }
236 /**
237 * Allow the user to override the function used for logging
238 */
239 if (typeof options.loggerFn === 'function') {
240 extra.log = options.loggerFn;
241 }
242 else if (options.loggerFn === false) {
243 extra.log = () => { };
244 }
245 if (typeof options.tsconfigRootDir === 'string') {
246 extra.tsconfigRootDir = options.tsconfigRootDir;
247 }
248 // NOTE - ensureAbsolutePath relies upon having the correct tsconfigRootDir in extra
249 extra.filePath = (0, shared_1.ensureAbsolutePath)(extra.filePath, extra);
250 if (Array.isArray(options.programs)) {
251 if (!options.programs.length) {
252 throw new Error(`You have set parserOptions.programs to an empty array. This will cause all files to not be found in existing programs. Either provide one or more existing TypeScript Program instances in the array, or remove the parserOptions.programs setting.`);
253 }
254 extra.programs = options.programs;
255 log('parserOptions.programs was provided, so parserOptions.project will be ignored.');
256 }
257 if (!extra.programs) {
258 // providing a program overrides project resolution
259 const projectFolderIgnoreList = ((_a = options.projectFolderIgnoreList) !== null && _a !== void 0 ? _a : ['**/node_modules/**'])
260 .reduce((acc, folder) => {
261 if (typeof folder === 'string') {
262 acc.push(folder);
263 }
264 return acc;
265 }, [])
266 // prefix with a ! for not match glob
267 .map(folder => (folder.startsWith('!') ? folder : `!${folder}`));
268 // NOTE - prepareAndTransformProjects relies upon having the correct tsconfigRootDir in extra
269 extra.projects = prepareAndTransformProjects(options.project, projectFolderIgnoreList);
270 }
271 if (Array.isArray(options.extraFileExtensions) &&
272 options.extraFileExtensions.every(ext => typeof ext === 'string')) {
273 extra.extraFileExtensions = options.extraFileExtensions;
274 }
275 /**
276 * Allow the user to enable or disable the preservation of the AST node maps
277 * during the conversion process.
278 */
279 if (typeof options.preserveNodeMaps === 'boolean') {
280 extra.preserveNodeMaps = options.preserveNodeMaps;
281 }
282 extra.createDefaultProgram =
283 typeof options.createDefaultProgram === 'boolean' &&
284 options.createDefaultProgram;
285 extra.EXPERIMENTAL_useSourceOfProjectReferenceRedirect =
286 typeof options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect ===
287 'boolean' && options.EXPERIMENTAL_useSourceOfProjectReferenceRedirect;
288 if (typeof options.moduleResolver === 'string') {
289 extra.moduleResolver = options.moduleResolver;
290 }
291}
292function warnAboutTSVersion() {
293 var _a;
294 if (!isRunningSupportedTypeScriptVersion && !warnedAboutTSVersion) {
295 const isTTY = typeof process === 'undefined' ? false : (_a = process.stdout) === null || _a === void 0 ? void 0 : _a.isTTY;
296 if (isTTY) {
297 const border = '=============';
298 const versionWarning = [
299 border,
300 'WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.',
301 'You may find that it works just fine, or you may not.',
302 `SUPPORTED TYPESCRIPT VERSIONS: ${SUPPORTED_TYPESCRIPT_VERSIONS}`,
303 `YOUR TYPESCRIPT VERSION: ${ACTIVE_TYPESCRIPT_VERSION}`,
304 'Please only submit bug reports when using the officially supported version.',
305 border,
306 ];
307 extra.log(versionWarning.join('\n\n'));
308 }
309 warnedAboutTSVersion = true;
310 }
311}
312/**
313 * ESLint (and therefore typescript-eslint) is used in both "single run"/one-time contexts,
314 * such as an ESLint CLI invocation, and long-running sessions (such as continuous feedback
315 * on a file in an IDE).
316 *
317 * When typescript-eslint handles TypeScript Program management behind the scenes, this distinction
318 * is important because there is significant overhead to managing the so called Watch Programs
319 * needed for the long-running use-case. We therefore use the following logic to figure out which
320 * of these contexts applies to the current execution.
321 */
322function inferSingleRun(options) {
323 // Allow users to explicitly inform us of their intent to perform a single run (or not) with TSESTREE_SINGLE_RUN
324 if (process.env.TSESTREE_SINGLE_RUN === 'false') {
325 extra.singleRun = false;
326 return;
327 }
328 if (process.env.TSESTREE_SINGLE_RUN === 'true') {
329 extra.singleRun = true;
330 return;
331 }
332 // Currently behind a flag while we gather real-world feedback
333 if (options === null || options === void 0 ? void 0 : options.allowAutomaticSingleRunInference) {
334 if (
335 // Default to single runs for CI processes. CI=true is set by most CI providers by default.
336 process.env.CI === 'true' ||
337 // This will be true for invocations such as `npx eslint ...` and `./node_modules/.bin/eslint ...`
338 process.argv[1].endsWith((0, path_1.normalize)('node_modules/.bin/eslint'))) {
339 extra.singleRun = true;
340 return;
341 }
342 }
343 /**
344 * We default to assuming that this run could be part of a long-running session (e.g. in an IDE)
345 * and watch programs will therefore be required
346 */
347 extra.singleRun = false;
348}
349function parse(code, options) {
350 const { ast } = parseWithNodeMapsInternal(code, options, false);
351 return ast;
352}
353exports.parse = parse;
354function parseWithNodeMapsInternal(code, options, shouldPreserveNodeMaps) {
355 /**
356 * Reset the parse configuration
357 */
358 resetExtra();
359 /**
360 * Ensure users do not attempt to use parse() when they need parseAndGenerateServices()
361 */
362 if (options === null || options === void 0 ? void 0 : options.errorOnTypeScriptSyntacticAndSemanticIssues) {
363 throw new Error(`"errorOnTypeScriptSyntacticAndSemanticIssues" is only supported for parseAndGenerateServices()`);
364 }
365 /**
366 * Ensure the source code is a string, and store a reference to it
367 */
368 code = enforceString(code);
369 extra.code = code;
370 /**
371 * Apply the given parser options
372 */
373 if (typeof options !== 'undefined') {
374 applyParserOptionsToExtra(options);
375 }
376 /**
377 * Warn if the user is using an unsupported version of TypeScript
378 */
379 warnAboutTSVersion();
380 /**
381 * Figure out whether this is a single run or part of a long-running process
382 */
383 inferSingleRun(options);
384 /**
385 * Create a ts.SourceFile directly, no ts.Program is needed for a simple
386 * parse
387 */
388 const ast = (0, createSourceFile_1.createSourceFile)(code, extra);
389 /**
390 * Convert the TypeScript AST to an ESTree-compatible one
391 */
392 const { estree, astMaps } = (0, ast_converter_1.astConverter)(ast, extra, shouldPreserveNodeMaps);
393 return {
394 ast: estree,
395 esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap,
396 tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap,
397 };
398}
399function parseWithNodeMaps(code, options) {
400 return parseWithNodeMapsInternal(code, options, true);
401}
402exports.parseWithNodeMaps = parseWithNodeMaps;
403let parseAndGenerateServicesCalls = {};
404// Privately exported utility intended for use in typescript-eslint unit tests only
405function clearParseAndGenerateServicesCalls() {
406 parseAndGenerateServicesCalls = {};
407}
408exports.clearParseAndGenerateServicesCalls = clearParseAndGenerateServicesCalls;
409function parseAndGenerateServices(code, options) {
410 var _a;
411 /**
412 * Reset the parse configuration
413 */
414 resetExtra();
415 /**
416 * Ensure the source code is a string, and store a reference to it
417 */
418 code = enforceString(code);
419 extra.code = code;
420 /**
421 * Apply the given parser options
422 */
423 if (typeof options !== 'undefined') {
424 applyParserOptionsToExtra(options);
425 if (typeof options.errorOnTypeScriptSyntacticAndSemanticIssues ===
426 'boolean' &&
427 options.errorOnTypeScriptSyntacticAndSemanticIssues) {
428 extra.errorOnTypeScriptSyntacticAndSemanticIssues = true;
429 }
430 }
431 /**
432 * Warn if the user is using an unsupported version of TypeScript
433 */
434 warnAboutTSVersion();
435 /**
436 * Figure out whether this is a single run or part of a long-running process
437 */
438 inferSingleRun(options);
439 /**
440 * If this is a single run in which the user has not provided any existing programs but there
441 * are programs which need to be created from the provided "project" option,
442 * create an Iterable which will lazily create the programs as needed by the iteration logic
443 */
444 if (extra.singleRun && !extra.programs && ((_a = extra.projects) === null || _a === void 0 ? void 0 : _a.length) > 0) {
445 extra.programs = {
446 *[Symbol.iterator]() {
447 for (const configFile of extra.projects) {
448 const existingProgram = existingPrograms.get(configFile);
449 if (existingProgram) {
450 yield existingProgram;
451 }
452 else {
453 log('Detected single-run/CLI usage, creating Program once ahead of time for project: %s', configFile);
454 const newProgram = (0, useProvidedPrograms_1.createProgramFromConfigFile)(configFile);
455 existingPrograms.set(configFile, newProgram);
456 yield newProgram;
457 }
458 }
459 },
460 };
461 }
462 /**
463 * Generate a full ts.Program or offer provided instances in order to be able to provide parser services, such as type-checking
464 */
465 const shouldProvideParserServices = extra.programs != null || (extra.projects && extra.projects.length > 0);
466 /**
467 * If we are in singleRun mode but the parseAndGenerateServices() function has been called more than once for the current file,
468 * 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
469 * 10 times in order to apply all possible fixes for the file).
470 *
471 * In this scenario we cannot rely upon the singleRun AOT compiled programs because the SourceFiles will not contain the source
472 * with the latest fixes applied. Therefore we fallback to creating the quickest possible isolated program from the updated source.
473 */
474 let ast;
475 let program;
476 if (extra.singleRun && options.filePath) {
477 parseAndGenerateServicesCalls[options.filePath] =
478 (parseAndGenerateServicesCalls[options.filePath] || 0) + 1;
479 }
480 if (extra.singleRun &&
481 options.filePath &&
482 parseAndGenerateServicesCalls[options.filePath] > 1) {
483 const isolatedAstAndProgram = (0, createIsolatedProgram_1.createIsolatedProgram)(code, extra);
484 ast = isolatedAstAndProgram.ast;
485 program = isolatedAstAndProgram.program;
486 }
487 else {
488 const astAndProgram = getProgramAndAST(code, extra.programs, shouldProvideParserServices, extra.createDefaultProgram);
489 ast = astAndProgram.ast;
490 program = astAndProgram.program;
491 }
492 /**
493 * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve
494 * mappings between converted and original AST nodes
495 */
496 const preserveNodeMaps = typeof extra.preserveNodeMaps === 'boolean' ? extra.preserveNodeMaps : true;
497 const { estree, astMaps } = (0, ast_converter_1.astConverter)(ast, extra, preserveNodeMaps);
498 /**
499 * Even if TypeScript parsed the source code ok, and we had no problems converting the AST,
500 * there may be other syntactic or semantic issues in the code that we can optionally report on.
501 */
502 if (program && extra.errorOnTypeScriptSyntacticAndSemanticIssues) {
503 const error = (0, semantic_or_syntactic_errors_1.getFirstSemanticOrSyntacticError)(program, ast);
504 if (error) {
505 throw (0, convert_1.convertError)(error);
506 }
507 }
508 /**
509 * Return the converted AST and additional parser services
510 */
511 return {
512 ast: estree,
513 services: {
514 hasFullTypeInformation: shouldProvideParserServices,
515 program,
516 esTreeNodeToTSNodeMap: astMaps.esTreeNodeToTSNodeMap,
517 tsNodeToESTreeNodeMap: astMaps.tsNodeToESTreeNodeMap,
518 },
519 };
520}
521exports.parseAndGenerateServices = parseAndGenerateServices;
522//# sourceMappingURL=parser.js.map
\No newline at end of file