Skip to content
This repository has been archived by the owner. It is now read-only.

feat: typed parser return value #33

Merged
merged 5 commits into from Nov 26, 2018
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -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,18 +162,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)) {
@@ -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<T extends ParserOptions = ParserOptions>(
code: string,
options?: T
) {
return generateAST<T>(code, options).estree;
}

export function parseAndGenerateServices(code: string, options: ParserOptions) {
@@ -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;
}
@@ -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
);
});
@@ -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);
});

ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.