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

feat: typed parser return value #33

Merged
merged 5 commits into from
Nov 26, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -53,9 +59,15 @@ function resetExtra(): void {
*/
function getASTFromProject(code: string, options: ParserOptions) {
return util.firstDefined(
calculateProjectParserOptions(code, options.filePath, extra),
calculateProjectParserOptions(
code,
options.filePath || (options.jsx ? 'estree.ts' : 'estree.tsx'),
extra
),
(currentProgram: ts.Program) => {
const ast = currentProgram.getSourceFile(options.filePath);
const ast = currentProgram.getSourceFile(
options.filePath || (options.jsx ? 'estree.ts' : 'estree.tsx')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filename logic is now separately defined 3 times within this file, can we promote it to be defined once at a higher level?

Other than that the PR LGTM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

);
return ast && { ast, program: currentProgram };
}
);
Expand Down Expand Up @@ -141,18 +153,37 @@ function getProgramAndAST(
// Parser
//------------------------------------------------------------------------------

type AST<T extends ParserOptions> = 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
* @param {boolean} shouldGenerateServices Flag determining whether to generate ast maps and program or not
* @param {ParserOptions} options configuration object for the parser
* @returns {Object} the AST
*/
function generateAST(
function generateAST<T extends ParserOptions = ParserOptions>(
code: string,
options: ParserOptions,
options: T = {} as T,
shouldGenerateServices = false
): any {
): {
estree: AST<T>;
program: typeof shouldGenerateServices extends true
? ts.Program
: (ts.Program | undefined);
astMaps: typeof shouldGenerateServices extends true
? {
esTreeNodeToTSNodeMap: WeakMap<object, any>;
tsNodeToESTreeNodeMap: WeakMap<object, any>;
}
: {
esTreeNodeToTSNodeMap?: WeakMap<object, any>;
tsNodeToESTreeNodeMap?: WeakMap<object, any>;
};
} {
const toString = String;

if (typeof code !== 'string' && !((code as any) instanceof String)) {
Expand Down Expand Up @@ -245,7 +276,7 @@ function generateAST(
estree,
program: shouldProvideParserServices ? program : undefined,
astMaps: shouldProvideParserServices
? astMaps
? astMaps!
: { esTreeNodeToTSNodeMap: undefined, tsNodeToESTreeNodeMap: undefined }
};
}
Expand All @@ -259,8 +290,11 @@ export { version };

const version = packageJSON.version;

export function parse(code: string, options: ParserOptions) {
return generateAST(code, options).estree;
export function parse<T extends ParserOptions = ParserOptions>(
code: string,
options?: T
) {
return generateAST<T>(code, options).estree;
}

export function parseAndGenerateServices(code: string, options: ParserOptions) {
Expand Down
22 changes: 11 additions & 11 deletions src/temp-types-based-on-js-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
27 changes: 14 additions & 13 deletions tests/lib/semanticInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,44 +79,44 @@ 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();
expect(tsArrayMember.kind).toBe(ts.SyntaxKind.NumericLiteral);
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
);
});
Expand All @@ -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);
});

Expand Down