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