diff --git a/packages/typescript-eslint-parser/src/parser.ts b/packages/typescript-eslint-parser/src/parser.ts index a10d2f115236..bdf4fd7d5f46 100644 --- a/packages/typescript-eslint-parser/src/parser.ts +++ b/packages/typescript-eslint-parser/src/parser.ts @@ -3,11 +3,19 @@ import * as typescriptESTree from 'typescript-estree'; import { analyzeScope } from './analyze-scope'; import { ParserOptions } from './parser-options'; import { visitorKeys } from './visitor-keys'; +import { Program } from 'typescript'; const packageJSON = require('../package.json'); +interface ParserServices { + program: Program | undefined; + esTreeNodeToTSNodeMap: WeakMap | undefined; + tsNodeToESTreeNodeMap: WeakMap | undefined; +} + interface ParseForESLintResult { ast: any; + services: ParserServices; visitorKeys: typeof visitorKeys; scopeManager: ReturnType; } @@ -43,7 +51,10 @@ export function parseForESLint( options.sourceType = 'script'; } - const ast = typescriptESTree.parse(code, options); + const { ast, services } = typescriptESTree.parseAndGenerateServices( + code, + options + ); ast.sourceType = options.sourceType; traverser.traverse(ast, { @@ -63,5 +74,5 @@ export function parseForESLint( }); const scopeManager = analyzeScope(ast, options); - return { ast, scopeManager, visitorKeys }; + return { ast, services, scopeManager, visitorKeys }; } diff --git a/packages/typescript-eslint-parser/tests/fixtures/services/isolated-file.src.ts b/packages/typescript-eslint-parser/tests/fixtures/services/isolated-file.src.ts new file mode 100644 index 000000000000..0f03f0973061 --- /dev/null +++ b/packages/typescript-eslint-parser/tests/fixtures/services/isolated-file.src.ts @@ -0,0 +1 @@ +const x = [3, 4, 5]; diff --git a/packages/typescript-eslint-parser/tests/fixtures/services/tsconfig.json b/packages/typescript-eslint-parser/tests/fixtures/services/tsconfig.json new file mode 100644 index 000000000000..914d17298c56 --- /dev/null +++ b/packages/typescript-eslint-parser/tests/fixtures/services/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true + } +} diff --git a/packages/typescript-eslint-parser/tests/lib/__snapshots__/services.ts.snap b/packages/typescript-eslint-parser/tests/lib/__snapshots__/services.ts.snap new file mode 100644 index 000000000000..d7af97b5b5dc --- /dev/null +++ b/packages/typescript-eslint-parser/tests/lib/__snapshots__/services.ts.snap @@ -0,0 +1,356 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`services fixtures/isolated-file.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "x", + "range": Array [ + 6, + 7, + ], + "type": "Identifier", + }, + "init": Object { + "elements": Array [ + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 12, + ], + "raw": "3", + "type": "Literal", + "value": 3, + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 1, + }, + "start": Object { + "column": 14, + "line": 1, + }, + }, + "range": Array [ + 14, + 15, + ], + "raw": "4", + "type": "Literal", + "value": 4, + }, + Object { + "loc": Object { + "end": Object { + "column": 18, + "line": 1, + }, + "start": Object { + "column": 17, + "line": 1, + }, + }, + "range": Array [ + 17, + 18, + ], + "raw": "5", + "type": "Literal", + "value": 5, + }, + ], + "loc": Object { + "end": Object { + "column": 19, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 19, + ], + "type": "ArrayExpression", + }, + "loc": Object { + "end": Object { + "column": 19, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 19, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 20, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 20, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [], + "loc": Object { + "end": Object { + "column": 0, + "line": 2, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 21, + ], + "sourceType": "module", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 5, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 7, + ], + "type": "Identifier", + "value": "x", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 1, + }, + "start": Object { + "column": 8, + "line": 1, + }, + }, + "range": Array [ + 8, + 9, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 11, + ], + "type": "Punctuator", + "value": "[", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 12, + ], + "type": "Numeric", + "value": "3", + }, + Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 12, + "line": 1, + }, + }, + "range": Array [ + 12, + 13, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 1, + }, + "start": Object { + "column": 14, + "line": 1, + }, + }, + "range": Array [ + 14, + 15, + ], + "type": "Numeric", + "value": "4", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 1, + }, + "start": Object { + "column": 15, + "line": 1, + }, + }, + "range": Array [ + 15, + 16, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 18, + "line": 1, + }, + "start": Object { + "column": 17, + "line": 1, + }, + }, + "range": Array [ + 17, + 18, + ], + "type": "Numeric", + "value": "5", + }, + Object { + "loc": Object { + "end": Object { + "column": 19, + "line": 1, + }, + "start": Object { + "column": 18, + "line": 1, + }, + }, + "range": Array [ + 18, + 19, + ], + "type": "Punctuator", + "value": "]", + }, + Object { + "loc": Object { + "end": Object { + "column": 20, + "line": 1, + }, + "start": Object { + "column": 19, + "line": 1, + }, + }, + "range": Array [ + 19, + 20, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; diff --git a/packages/typescript-eslint-parser/tests/lib/comments.ts b/packages/typescript-eslint-parser/tests/lib/comments.ts index 271f1dd1d15d..bdf3356f3a4b 100644 --- a/packages/typescript-eslint-parser/tests/lib/comments.ts +++ b/packages/typescript-eslint-parser/tests/lib/comments.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import glob from 'glob'; import * as testUtils from '../../tools/test-utils'; +import { ParserOptions } from '../../src/parser-options'; const FIXTURES_DIR = '../../node_modules/@typescript-eslint/shared-fixtures/fixtures/comments'; @@ -13,7 +14,7 @@ const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.js`); describe('Comments', () => { testFiles.forEach(filename => { const code = fs.readFileSync(filename, 'utf8'); - const config = { + const config: ParserOptions = { jsx: true, sourceType: 'module' }; diff --git a/packages/typescript-eslint-parser/tests/lib/parser.ts b/packages/typescript-eslint-parser/tests/lib/parser.ts index 682024905f56..89ad87638db8 100644 --- a/packages/typescript-eslint-parser/tests/lib/parser.ts +++ b/packages/typescript-eslint-parser/tests/lib/parser.ts @@ -14,7 +14,7 @@ describe('parser', () => { it('parseForESLint() should set the sourceType to script, if an invalid one is provided', () => { const code = 'const valid = true;'; - const spy = jest.spyOn(typescriptESTree, 'parse'); + const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); parseForESLint(code, { sourceType: 'foo' as any }); expect(spy).toHaveBeenCalledWith(code, { sourceType: 'script', diff --git a/packages/typescript-eslint-parser/tests/lib/services.ts b/packages/typescript-eslint-parser/tests/lib/services.ts new file mode 100644 index 000000000000..ec9ae76f13c5 --- /dev/null +++ b/packages/typescript-eslint-parser/tests/lib/services.ts @@ -0,0 +1,42 @@ +import path from 'path'; +import fs from 'fs'; +import glob from 'glob'; +import * as testUtils from '../../tools/test-utils'; + +//------------------------------------------------------------------------------ +// Setup +//------------------------------------------------------------------------------ + +const FIXTURES_DIR = './tests/fixtures/services'; +const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.ts`); + +function createConfig(filename: string): object { + return { + filePath: filename, + generateServices: true, + project: './tsconfig.json', + tsconfigRootDir: path.resolve(FIXTURES_DIR) + }; +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe('services', () => { + testFiles.forEach(filename => { + const code = fs.readFileSync(filename, 'utf8'); + const config = createConfig(filename); + it( + testUtils.formatSnapshotName(filename, FIXTURES_DIR, '.ts'), + testUtils.createSnapshotTestBlock(code, config) + ); + it(`${testUtils.formatSnapshotName( + filename, + FIXTURES_DIR, + '.ts' + )} services`, () => { + testUtils.testServices(code, config); + }); + }); +}); diff --git a/packages/typescript-eslint-parser/tools/test-utils.ts b/packages/typescript-eslint-parser/tools/test-utils.ts index 328619764805..8fb82408df4f 100644 --- a/packages/typescript-eslint-parser/tools/test-utils.ts +++ b/packages/typescript-eslint-parser/tools/test-utils.ts @@ -1,4 +1,15 @@ import * as parser from '../src/parser'; +import { ParserOptions } from '../src/parser-options'; + +const defaultConfig = { + loc: true, + range: true, + raw: true, + tokens: true, + comment: true, + errorOnUnknownASTType: true, + sourceType: 'module' +}; /** * Returns a raw copy of the given AST @@ -20,19 +31,10 @@ function getRaw(ast: any) { * Returns a function which can be used as the callback of a Jest test() block, * and which performs an assertion on the snapshot for the given code and config. * @param {string} code The source code to parse - * @param {*} config the parser configuration + * @param {ParserOptions} config the parser configuration * @returns {Function} callback for Jest test() block */ -export function createSnapshotTestBlock(code: any, config = {}) { - const defaultConfig = { - loc: true, - range: true, - raw: true, - tokens: true, - comment: true, - errorOnUnknownASTType: true, - sourceType: 'module' - }; +export function createSnapshotTestBlock(code: any, config: ParserOptions = {}) { config = Object.assign({}, defaultConfig, config); /** @@ -60,6 +62,21 @@ export function createSnapshotTestBlock(code: any, config = {}) { }; } +/** + * @param {string} code The code being parsed + * @param {ParserOptions} config The configuration object for the parser + * @returns {void} + */ +export function testServices(code: string, config: ParserOptions = {}) { + config = Object.assign({}, defaultConfig, config); + + const services = parser.parseForESLint(code, config).services; + expect(services).toBeDefined(); + expect(services.program).toBeDefined(); + expect(services.esTreeNodeToTSNodeMap).toBeDefined(); + expect(services.tsNodeToESTreeNodeMap).toBeDefined(); +} + export function formatSnapshotName( filename: any, fixturesDir: any,