• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { ParserOptions, TSESTree, Lib } from '@typescript-eslint/types';
2import {
3  parseAndGenerateServices,
4  ParserServices,
5  TSESTreeOptions,
6  visitorKeys,
7} from '@typescript-eslint/typescript-estree';
8import {
9  analyze,
10  AnalyzeOptions,
11  ScopeManager,
12} from '@typescript-eslint/scope-manager';
13import debug from 'debug';
14import { CompilerOptions, ScriptTarget } from 'typescript';
15
16const log = debug('typescript-eslint:parser:parser');
17
18interface ParseForESLintResult {
19  ast: TSESTree.Program & {
20    range?: [number, number];
21    tokens?: TSESTree.Token[];
22    comments?: TSESTree.Comment[];
23  };
24  services: ParserServices;
25  visitorKeys: typeof visitorKeys;
26  scopeManager: ScopeManager;
27}
28
29function validateBoolean(
30  value: boolean | undefined,
31  fallback = false,
32): boolean {
33  if (typeof value !== 'boolean') {
34    return fallback;
35  }
36  return value;
37}
38
39const LIB_FILENAME_REGEX = /lib\.(.+)\.d\.ts$/;
40function getLib(compilerOptions: CompilerOptions): Lib[] {
41  if (compilerOptions.lib) {
42    return compilerOptions.lib.reduce((acc, lib) => {
43      const match = LIB_FILENAME_REGEX.exec(lib.toLowerCase());
44      if (match) {
45        acc.push(match[1] as Lib);
46      }
47
48      return acc;
49    }, [] as Lib[]);
50  }
51
52  const target = compilerOptions.target ?? ScriptTarget.ES5;
53  // https://github.com/Microsoft/TypeScript/blob/59ad375234dc2efe38d8ee0ba58414474c1d5169/src/compiler/utilitiesPublic.ts#L13-L32
54  switch (target) {
55    case ScriptTarget.ESNext:
56      return ['esnext.full'];
57    case ScriptTarget.ES2020:
58      return ['es2020.full'];
59    case ScriptTarget.ES2019:
60      return ['es2019.full'];
61    case ScriptTarget.ES2018:
62      return ['es2018.full'];
63    case ScriptTarget.ES2017:
64      return ['es2017.full'];
65    case ScriptTarget.ES2016:
66      return ['es2016.full'];
67    case ScriptTarget.ES2015:
68      return ['es6'];
69    default:
70      return ['lib'];
71  }
72}
73
74function parse(
75  code: string,
76  options?: ParserOptions,
77): ParseForESLintResult['ast'] {
78  return parseForESLint(code, options).ast;
79}
80
81function parseForESLint(
82  code: string,
83  options?: ParserOptions | null,
84): ParseForESLintResult {
85  if (!options || typeof options !== 'object') {
86    options = {};
87  } else {
88    options = { ...options };
89  }
90  // https://eslint.org/docs/user-guide/configuring#specifying-parser-options
91  // if sourceType is not provided by default eslint expect that it will be set to "script"
92  if (options.sourceType !== 'module' && options.sourceType !== 'script') {
93    options.sourceType = 'script';
94  }
95  if (typeof options.ecmaFeatures !== 'object') {
96    options.ecmaFeatures = {};
97  }
98
99  const parserOptions: TSESTreeOptions = {};
100  Object.assign(parserOptions, options, {
101    useJSXTextNode: validateBoolean(options.useJSXTextNode, true),
102    jsx: validateBoolean(options.ecmaFeatures.jsx),
103  });
104  const analyzeOptions: AnalyzeOptions = {
105    ecmaVersion: options.ecmaVersion,
106    globalReturn: options.ecmaFeatures.globalReturn,
107    jsxPragma: options.jsxPragma,
108    jsxFragmentName: options.jsxFragmentName,
109    lib: options.lib,
110    sourceType: options.sourceType,
111  };
112
113  if (typeof options.filePath === 'string') {
114    const tsx = options.filePath.endsWith('.tsx');
115    if (tsx || options.filePath.endsWith('.ts')) {
116      parserOptions.jsx = tsx;
117    }
118  }
119
120  /**
121   * Allow the user to suppress the warning from typescript-estree if they are using an unsupported
122   * version of TypeScript
123   */
124  const warnOnUnsupportedTypeScriptVersion = validateBoolean(
125    options.warnOnUnsupportedTypeScriptVersion,
126    true,
127  );
128  if (!warnOnUnsupportedTypeScriptVersion) {
129    parserOptions.loggerFn = false;
130  }
131
132  const { ast, services } = parseAndGenerateServices(code, parserOptions);
133  ast.sourceType = options.sourceType;
134
135  if (services.hasFullTypeInformation) {
136    // automatically apply the options configured for the program
137    const compilerOptions = services.program.getCompilerOptions();
138    if (analyzeOptions.lib == null) {
139      analyzeOptions.lib = getLib(compilerOptions);
140      log('Resolved libs from program: %o', analyzeOptions.lib);
141    }
142    if (parserOptions.jsx === true) {
143      if (
144        analyzeOptions.jsxPragma === undefined &&
145        compilerOptions.jsxFactory != null
146      ) {
147        // in case the user has specified something like "preact.h"
148        const factory = compilerOptions.jsxFactory.split('.')[0].trim();
149        analyzeOptions.jsxPragma = factory;
150        log('Resolved jsxPragma from program: %s', analyzeOptions.jsxPragma);
151      }
152      if (
153        analyzeOptions.jsxFragmentName === undefined &&
154        compilerOptions.jsxFragmentFactory != null
155      ) {
156        // in case the user has specified something like "preact.Fragment"
157        const fragFactory = compilerOptions.jsxFragmentFactory
158          .split('.')[0]
159          .trim();
160        analyzeOptions.jsxFragmentName = fragFactory;
161        log(
162          'Resolved jsxFragmentName from program: %s',
163          analyzeOptions.jsxFragmentName,
164        );
165      }
166    }
167  }
168
169  const scopeManager = analyze(ast, analyzeOptions);
170
171  return { ast, services, scopeManager, visitorKeys };
172}
173
174export { parse, parseForESLint, ParserOptions };
175