Skip to content

Commit

Permalink
Fix: Gracefully handle missing TypeScript dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
antross authored and molant committed Oct 30, 2019
1 parent a4432bf commit a55a667
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 15 deletions.
2 changes: 2 additions & 0 deletions packages/parser-typescript/package.json
Expand Up @@ -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",
Expand All @@ -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"
},
Expand Down
43 changes: 28 additions & 15 deletions 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';
Expand All @@ -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;
}
}
}

Expand All @@ -37,6 +46,10 @@ export default class TypeScriptParser extends Parser<ScriptEvents> {
return;
}

if (!TypeScriptESTree) {
return;
}

debug(`Parsing TypeScript file: ${resource}`);

const sourceCode = response.body.content;
Expand All @@ -45,7 +58,7 @@ export default class TypeScriptParser extends Parser<ScriptEvents> {
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', {
Expand Down
8 changes: 8 additions & 0 deletions packages/parser-typescript/tests/tests.ts
Expand Up @@ -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<ScriptEvents & HTMLEvents>, content: string) => {
Expand Down Expand Up @@ -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 };
Expand Down

0 comments on commit a55a667

Please sign in to comment.