Skip to content
This repository was archived by the owner on Jan 14, 2019. It is now read-only.

Commit 7599aba

Browse files
g-planeJamesHenry
authored andcommitted
feat: typed parser return value (#33)
1 parent 8e70375 commit 7599aba

File tree

3 files changed

+78
-34
lines changed

3 files changed

+78
-34
lines changed

src/parser.ts

+53-10
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ import calculateProjectParserOptions from './tsconfig-parser';
99
import semver from 'semver';
1010
import ts from 'typescript';
1111
import convert from './ast-converter';
12+
import {
13+
Extra,
14+
ParserOptions,
15+
ESTreeToken,
16+
ESTreeComment
17+
} from './temp-types-based-on-js-source';
18+
import { Program } from './estree/spec';
1219
import util from './node-utils';
13-
import { Extra, ParserOptions } from './temp-types-based-on-js-source';
1420

1521
const packageJSON = require('../package.json');
1622

@@ -24,6 +30,15 @@ const isRunningSupportedTypeScriptVersion = semver.satisfies(
2430
let extra: Extra;
2531
let warnedAboutTSVersion = false;
2632

33+
/**
34+
* Compute the filename based on the parser options
35+
*
36+
* @param options Parser options
37+
*/
38+
function getFileName({ jsx }: { jsx?: boolean }) {
39+
return jsx ? 'estree.tsx' : 'estree.ts';
40+
}
41+
2742
/**
2843
* Resets the extra config object
2944
* @returns {void}
@@ -53,9 +68,15 @@ function resetExtra(): void {
5368
*/
5469
function getASTFromProject(code: string, options: ParserOptions) {
5570
return util.firstDefined(
56-
calculateProjectParserOptions(code, options.filePath, extra),
71+
calculateProjectParserOptions(
72+
code,
73+
options.filePath || getFileName(options),
74+
extra
75+
),
5776
(currentProgram: ts.Program) => {
58-
const ast = currentProgram.getSourceFile(options.filePath);
77+
const ast = currentProgram.getSourceFile(
78+
options.filePath || getFileName(options)
79+
);
5980
return ast && { ast, program: currentProgram };
6081
}
6182
);
@@ -68,7 +89,7 @@ function getASTFromProject(code: string, options: ParserOptions) {
6889
function createNewProgram(code: string) {
6990
// Even if jsx option is set in typescript compiler, filename still has to
7091
// contain .tsx file extension
71-
const FILENAME = extra.jsx ? 'estree.tsx' : 'estree.ts';
92+
const FILENAME = getFileName(extra);
7293

7394
const compilerHost = {
7495
fileExists() {
@@ -141,18 +162,37 @@ function getProgramAndAST(
141162
// Parser
142163
//------------------------------------------------------------------------------
143164

165+
type AST<T extends ParserOptions> = Program &
166+
(T['range'] extends true ? { range: [number, number] } : {}) &
167+
(T['tokens'] extends true ? { tokens: ESTreeToken[] } : {}) &
168+
(T['comment'] extends true ? { comments: ESTreeComment[] } : {});
169+
144170
/**
145171
* Parses the given source code to produce a valid AST
146172
* @param {string} code TypeScript code
147173
* @param {boolean} shouldGenerateServices Flag determining whether to generate ast maps and program or not
148174
* @param {ParserOptions} options configuration object for the parser
149175
* @returns {Object} the AST
150176
*/
151-
function generateAST(
177+
function generateAST<T extends ParserOptions = ParserOptions>(
152178
code: string,
153-
options: ParserOptions,
179+
options: T = {} as T,
154180
shouldGenerateServices = false
155-
): any {
181+
): {
182+
estree: AST<T>;
183+
program: typeof shouldGenerateServices extends true
184+
? ts.Program
185+
: (ts.Program | undefined);
186+
astMaps: typeof shouldGenerateServices extends true
187+
? {
188+
esTreeNodeToTSNodeMap: WeakMap<object, any>;
189+
tsNodeToESTreeNodeMap: WeakMap<object, any>;
190+
}
191+
: {
192+
esTreeNodeToTSNodeMap?: WeakMap<object, any>;
193+
tsNodeToESTreeNodeMap?: WeakMap<object, any>;
194+
};
195+
} {
156196
const toString = String;
157197

158198
if (typeof code !== 'string' && !((code as any) instanceof String)) {
@@ -245,7 +285,7 @@ function generateAST(
245285
estree,
246286
program: shouldProvideParserServices ? program : undefined,
247287
astMaps: shouldProvideParserServices
248-
? astMaps
288+
? astMaps!
249289
: { esTreeNodeToTSNodeMap: undefined, tsNodeToESTreeNodeMap: undefined }
250290
};
251291
}
@@ -259,8 +299,11 @@ export { version };
259299

260300
const version = packageJSON.version;
261301

262-
export function parse(code: string, options: ParserOptions) {
263-
return generateAST(code, options).estree;
302+
export function parse<T extends ParserOptions = ParserOptions>(
303+
code: string,
304+
options?: T
305+
) {
306+
return generateAST<T>(code, options).estree;
264307
}
265308

266309
export function parseAndGenerateServices(code: string, options: ParserOptions) {

src/temp-types-based-on-js-source.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ export interface Extra {
7171
}
7272

7373
export interface ParserOptions {
74-
range: boolean;
75-
loc: boolean;
76-
tokens: boolean;
77-
comment: boolean;
78-
jsx: boolean;
79-
errorOnUnknownASTType: boolean;
80-
useJSXTextNode: boolean;
81-
loggerFn: Function | false;
82-
project: string | string[];
83-
filePath: string;
84-
tsconfigRootDir: string;
74+
range?: boolean;
75+
loc?: boolean;
76+
tokens?: boolean;
77+
comment?: boolean;
78+
jsx?: boolean;
79+
errorOnUnknownASTType?: boolean;
80+
useJSXTextNode?: boolean;
81+
loggerFn?: Function | false;
82+
project?: string | string[];
83+
filePath?: string;
84+
tsconfigRootDir?: string;
8585
}

tests/lib/semanticInfo.ts

+14-13
Original file line numberDiff line numberDiff line change
@@ -79,44 +79,44 @@ describe('semanticInfo', () => {
7979

8080
// get type checker
8181
expect(parseResult).toHaveProperty('services.program.getTypeChecker');
82-
const checker = parseResult.services.program.getTypeChecker();
82+
const checker = parseResult.services.program!.getTypeChecker();
8383

8484
// get number node (ast shape validated by snapshot)
85-
const arrayMember =
86-
parseResult.ast.body[0].declarations[0].init.elements[0];
85+
const arrayMember = (parseResult.ast as any).body[0].declarations[0].init
86+
.elements[0];
8787
expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap');
8888

8989
// get corresponding TS node
90-
const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap.get(
90+
const tsArrayMember = parseResult.services.esTreeNodeToTSNodeMap!.get(
9191
arrayMember
9292
);
9393
expect(tsArrayMember).toBeDefined();
9494
expect(tsArrayMember.kind).toBe(ts.SyntaxKind.NumericLiteral);
9595
expect(tsArrayMember.text).toBe('3');
9696

9797
// get type of TS node
98-
const arrayMemberType = checker.getTypeAtLocation(tsArrayMember);
98+
const arrayMemberType: any = checker.getTypeAtLocation(tsArrayMember);
9999
expect(arrayMemberType.flags).toBe(ts.TypeFlags.NumberLiteral);
100100
expect(arrayMemberType.value).toBe(3);
101101

102102
// make sure it maps back to original ESTree node
103103
expect(parseResult).toHaveProperty('services.tsNodeToESTreeNodeMap');
104-
expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsArrayMember)).toBe(
104+
expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsArrayMember)).toBe(
105105
arrayMember
106106
);
107107

108108
// get bound name
109-
const boundName = parseResult.ast.body[0].declarations[0].id;
109+
const boundName = (parseResult.ast as any).body[0].declarations[0].id;
110110
expect(boundName.name).toBe('x');
111111

112-
const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap.get(
112+
const tsBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get(
113113
boundName
114114
);
115115
expect(tsBoundName).toBeDefined();
116116

117117
checkNumberArrayType(checker, tsBoundName);
118118

119-
expect(parseResult.services.tsNodeToESTreeNodeMap.get(tsBoundName)).toBe(
119+
expect(parseResult.services.tsNodeToESTreeNodeMap!.get(tsBoundName)).toBe(
120120
boundName
121121
);
122122
});
@@ -130,22 +130,23 @@ describe('semanticInfo', () => {
130130

131131
// get type checker
132132
expect(parseResult).toHaveProperty('services.program.getTypeChecker');
133-
const checker = parseResult.services.program.getTypeChecker();
133+
const checker = parseResult.services.program!.getTypeChecker();
134134

135135
// get array node (ast shape validated by snapshot)
136136
// node is defined in other file than the parsed one
137-
const arrayBoundName = parseResult.ast.body[1].expression.callee.object;
137+
const arrayBoundName = (parseResult.ast as any).body[1].expression.callee
138+
.object;
138139
expect(arrayBoundName.name).toBe('arr');
139140

140141
expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap');
141-
const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap.get(
142+
const tsArrayBoundName = parseResult.services.esTreeNodeToTSNodeMap!.get(
142143
arrayBoundName
143144
);
144145
expect(tsArrayBoundName).toBeDefined();
145146
checkNumberArrayType(checker, tsArrayBoundName);
146147

147148
expect(
148-
parseResult.services.tsNodeToESTreeNodeMap.get(tsArrayBoundName)
149+
parseResult.services.tsNodeToESTreeNodeMap!.get(tsArrayBoundName)
149150
).toBe(arrayBoundName);
150151
});
151152

0 commit comments

Comments
 (0)