diff --git a/packages/parser-typescript/package.json b/packages/parser-typescript/package.json index 203cfe36b7d..b6f2a334cca 100644 --- a/packages/parser-typescript/package.json +++ b/packages/parser-typescript/package.json @@ -17,6 +17,7 @@ "@hint/parser-html": "^3.0.11", "@hint/parser-jsx": "^1.0.0", "@types/node": "^12.7.5", + "@types/proxyquire": "^1.3.28", "@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/parser": "^1.12.0", "ava": "^2.4.0", @@ -26,6 +27,7 @@ "eventemitter2": "^5.0.1", "npm-run-all": "^4.1.5", "nyc": "^14.1.0", + "proxyquire": "^2.1.3", "rimraf": "^3.0.0", "typescript": "^3.6.4" }, diff --git a/packages/parser-typescript/src/parser.ts b/packages/parser-typescript/src/parser.ts index 17f019876b2..c295dced830 100644 --- a/packages/parser-typescript/src/parser.ts +++ b/packages/parser-typescript/src/parser.ts @@ -1,7 +1,6 @@ /** * @fileoverview webhint parser needed to analyze TypeScript files. */ -import { parse, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; import { debug as d } from '@hint/utils/dist/src/debug'; import { Parser } from 'hint/dist/src/lib/types'; import { Engine } from 'hint/dist/src/lib/engine'; @@ -10,20 +9,30 @@ import { base, combineWalk } from '@hint/parser-javascript/dist/src/walk'; const debug = d(__filename); -// Extend `walk` to skip over most TS-specific nodes. -for (const type of Object.keys(AST_NODE_TYPES)) { - // Ensure `value` of `ClassProperty` instances is walked. - if (type === 'ClassProperty') { - base[type] = (node: any, st: any, c: any) => { - if (node.value) { - c(node.value, st); - } - }; - } +let TypeScriptESTree: typeof import('@typescript-eslint/typescript-estree') | null = null; + +try { + TypeScriptESTree = require('@typescript-eslint/typescript-estree'); +} catch (e) { + debug(`Unable to load TypeScript parser: ${e}`); +} - // Just ignore anything else - if (!base[type]) { - base[type] = base.Identifier; +if (TypeScriptESTree) { + // Extend `walk` to skip over most TS-specific nodes. + for (const type of Object.keys(TypeScriptESTree.AST_NODE_TYPES)) { + // Ensure `value` of `ClassProperty` instances is walked. + if (type === 'ClassProperty') { + base[type] = (node: any, st: any, c: any) => { + if (node.value) { + c(node.value, st); + } + }; + } + + // Just ignore anything else + if (!base[type]) { + base[type] = base.Identifier; + } } } @@ -37,6 +46,10 @@ export default class TypeScriptParser extends Parser { return; } + if (!TypeScriptESTree) { + return; + } + debug(`Parsing TypeScript file: ${resource}`); const sourceCode = response.body.content; @@ -45,7 +58,7 @@ export default class TypeScriptParser extends Parser { try { await engine.emitAsync('parse::start::javascript', { resource }); - const result = parse(sourceCode, { jsx, loc: true, useJSXTextNode: jsx }); + const result = TypeScriptESTree.parse(sourceCode, { jsx, loc: true, useJSXTextNode: jsx }); await combineWalk(async (walk) => { await engine.emitAsync('parse::end::javascript', { diff --git a/packages/parser-typescript/tests/tests.ts b/packages/parser-typescript/tests/tests.ts index e224c034b5a..9581e91f6bf 100644 --- a/packages/parser-typescript/tests/tests.ts +++ b/packages/parser-typescript/tests/tests.ts @@ -7,6 +7,8 @@ import { CallExpression, ScriptEvents } from '@hint/parser-javascript'; import { base } from '@hint/parser-javascript/dist/src/walk'; import JSXParser from '@hint/parser-jsx'; +import proxyquire = require('proxyquire'); + import TypeScriptParser from '../src/parser'; const emitFetchEndTSX = async (engine: Engine, content: string) => { @@ -79,6 +81,12 @@ const walkCallExpressions = async (content: string) => { return nodes; }; +test('It gracefully handles a missing TypeScript dependency', (t) => { + t.notThrows(() => { + proxyquire('../src/parser', { '@typescript-eslint/typescript-estree': null }); + }); +}); + test('It can parse and skip types to walk ESTree AST nodes', async (t) => { const nodes = await walkCallExpressions(` type Foo = { bar: string };