diff --git a/src/parser.ts b/src/parser.ts index 1a48475..41135b8 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -9,8 +9,14 @@ import calculateProjectParserOptions from './tsconfig-parser'; import semver from 'semver'; import ts from 'typescript'; import convert from './ast-converter'; +import { + Extra, + ParserOptions, + ESTreeToken, + ESTreeComment +} from './temp-types-based-on-js-source'; +import { Program } from './estree/spec'; import util from './node-utils'; -import { Extra, ParserOptions } from './temp-types-based-on-js-source'; const packageJSON = require('../package.json'); @@ -24,6 +30,15 @@ const isRunningSupportedTypeScriptVersion = semver.satisfies( let extra: Extra; let warnedAboutTSVersion = false; +/** + * Compute the filename based on the parser options + * + * @param options Parser options + */ +function getFileName({ jsx }: { jsx?: boolean }) { + return jsx ? 'estree.tsx' : 'estree.ts'; +} + /** * Resets the extra config object * @returns {void} @@ -53,9 +68,15 @@ function resetExtra(): void { */ function getASTFromProject(code: string, options: ParserOptions) { return util.firstDefined( - calculateProjectParserOptions(code, options.filePath, extra), + calculateProjectParserOptions( + code, + options.filePath || getFileName(options), + extra + ), (currentProgram: ts.Program) => { - const ast = currentProgram.getSourceFile(options.filePath); + const ast = currentProgram.getSourceFile( + options.filePath || getFileName(options) + ); return ast && { ast, program: currentProgram }; } ); @@ -68,7 +89,7 @@ function getASTFromProject(code: string, options: ParserOptions) { function createNewProgram(code: string) { // Even if jsx option is set in typescript compiler, filename still has to // contain .tsx file extension - const FILENAME = extra.jsx ? 'estree.tsx' : 'estree.ts'; + const FILENAME = getFileName(extra); const compilerHost = { fileExists() { @@ -141,6 +162,11 @@ function getProgramAndAST( // Parser //------------------------------------------------------------------------------ +type AST = Program & + (T['range'] extends true ? { range: [number, number] } : {}) & + (T['tokens'] extends true ? { tokens: ESTreeToken[] } : {}) & + (T['comment'] extends true ? { comments: ESTreeComment[] } : {}); + /** * Parses the given source code to produce a valid AST * @param {string} code TypeScript code @@ -148,11 +174,25 @@ function getProgramAndAST( * @param {ParserOptions} options configuration object for the parser * @returns {Object} the AST */ -function generateAST( +function generateAST( code: string, - options: ParserOptions, + options: T = {} as T, shouldGenerateServices = false -): any { +): { + estree: AST; + program: typeof shouldGenerateServices extends true + ? ts.Program + : (ts.Program | undefined); + astMaps: typeof shouldGenerateServices extends true + ? { + esTreeNodeToTSNodeMap: WeakMap; + tsNodeToESTreeNodeMap: WeakMap; + } + : { + esTreeNodeToTSNodeMap?: WeakMap; + tsNodeToESTreeNodeMap?: WeakMap; + }; +} { const toString = String; if (typeof code !== 'string' && !((code as any) instanceof String)) { @@ -245,7 +285,7 @@ function generateAST( estree, program: shouldProvideParserServices ? program : undefined, astMaps: shouldProvideParserServices - ? astMaps + ? astMaps! : { esTreeNodeToTSNodeMap: undefined, tsNodeToESTreeNodeMap: undefined } }; } @@ -259,8 +299,11 @@ export { version }; const version = packageJSON.version; -export function parse(code: string, options: ParserOptions) { - return generateAST(code, options).estree; +export function parse( + code: string, + options?: T +) { + return generateAST(code, options).estree; } export function parseAndGenerateServices(code: string, options: ParserOptions) { diff --git a/src/temp-types-based-on-js-source.ts b/src/temp-types-based-on-js-source.ts index f1afe55..b5db5cb 100644 --- a/src/temp-types-based-on-js-source.ts +++ b/src/temp-types-based-on-js-source.ts @@ -71,15 +71,15 @@ export interface Extra { } export interface ParserOptions { - range: boolean; - loc: boolean; - tokens: boolean; - comment: boolean; - jsx: boolean; - errorOnUnknownASTType: boolean; - useJSXTextNode: boolean; - loggerFn: Function | false; - project: string | string[]; - filePath: string; - tsconfigRootDir: string; + range?: boolean; + loc?: boolean; + tokens?: boolean; + comment?: boolean; + jsx?: boolean; + errorOnUnknownASTType?: boolean; + useJSXTextNode?: boolean; + loggerFn?: Function | false; + project?: string | string[]; + filePath?: string; + tsconfigRootDir?: string; } diff --git a/tests/lib/semanticInfo.ts b/tests/lib/semanticInfo.ts index 5dafc92..d0c1f37 100644 --- a/tests/lib/semanticInfo.ts +++ b/tests/lib/semanticInfo.ts @@ -79,15 +79,15 @@ describe('semanticInfo', () => { // get type checker expect(parseResult).toHaveProperty('services.program.getTypeChecker'); - const checker = parseResult.services.program.getTypeChecker(); + const checker = parseResult.services.program!.getTypeChecker(); // get number node (ast shape validated by snapshot) - const arrayMember = - parseResult.ast.body[0].declarations[0].init.elements[0]; + const arrayMember = (parseResult.ast as any).body[0].declarations[0].init + .elements[0]; expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); // get corresponding TS node - const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap.get( + const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap!.get( arrayMember ); expect(tsArrayMember).toBeDefined(); @@ -95,28 +95,28 @@ describe('semanticInfo', () => { expect(tsArrayMember.text).toBe('3'); // get type of TS node - const arrayMemberType = checker.getTypeAtLocation(tsArrayMember); + const arrayMemberType: any = checker.getTypeAtLocation(tsArrayMember); expect(arrayMemberType.flags).toBe(ts.TypeFlags.NumberLiteral); expect(arrayMemberType.value).toBe(3); // make sure it maps back to original ESTree node expect(parseResult).toHaveProperty('services.tsNodeToESTreeNodeMap'); - expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsArrayMember)).toBe( + expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsArrayMember)).toBe( arrayMember ); // get bound name - const boundName = parseResult.ast.body[0].declarations[0].id; + const boundName = (parseResult.ast as any).body[0].declarations[0].id; expect(boundName.name).toBe('x'); - const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap.get( + const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get( boundName ); expect(tsBoundName).toBeDefined(); checkNumberArrayType(checker, tsBoundName); - expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsBoundName)).toBe( + expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsBoundName)).toBe( boundName ); }); @@ -130,22 +130,23 @@ describe('semanticInfo', () => { // get type checker expect(parseResult).toHaveProperty('services.program.getTypeChecker'); - const checker = parseResult.services.program.getTypeChecker(); + const checker = parseResult.services.program!.getTypeChecker(); // get array node (ast shape validated by snapshot) // node is defined in other file than the parsed one - const arrayBoundName = parseResult.ast.body[1].expression.callee.object; + const arrayBoundName = (parseResult.ast as any).body[1].expression.callee + .object; expect(arrayBoundName.name).toBe('arr'); expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); - const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap.get( + const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get( arrayBoundName ); expect(tsArrayBoundName).toBeDefined(); checkNumberArrayType(checker, tsArrayBoundName); expect( - parseResult.services.tsNodeToESTreeNodeMap.get(tsArrayBoundName) + parseResult.services.tsNodeToESTreeNodeMap!.get(tsArrayBoundName) ).toBe(arrayBoundName); });