Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phenomnomnominal/6d4fbb6ca676214e81c1bc2f65dcf78e to your computer and use it in GitHub Desktop.
Save phenomnomnominal/6d4fbb6ca676214e81c1bc2f65dcf78e to your computer and use it in GitHub Desktop.
TSLint rule with TSQuery
// Test Utilities:
import { ast, expect } from './index';
// Under test:
import { Rule } from '../src/noTypeofBrowserGlobalRule';
describe('no-typeof-browser-global', () => {
it('should fail when `typeof` is used to check for "document"', () => {
const sourceFile = ast(`
typeof document === 'undefined';
`);
const errors = Rule.prototype.apply(sourceFile);
const [error] = errors;
expect(errors.length).to.equal(1);
expect(error.getFailure()).to.include(`It's a bad idea to use 'typeof' to check for access to the 'document' API.`);
});
it('should fail when `typeof` is used to check for "location"', () => {
const sourceFile = ast(`
typeof location === 'undefined';
`);
const errors = Rule.prototype.apply(sourceFile);
const [error] = errors;
expect(errors.length).to.equal(1);
expect(error.getFailure()).to.include(`It's a bad idea to use 'typeof' to check for access to the 'location' API.`);
});
it('should fail when `typeof` is used to check for "navigator"', () => {
const sourceFile = ast(`
typeof navigator === 'undefined';
`);
const errors = Rule.prototype.apply(sourceFile);
const [error] = errors;
expect(errors.length).to.equal(1);
expect(error.getFailure()).to.include(`It's a bad idea to use 'typeof' to check for access to the 'navigator' API.`);
});
it('should fail when `typeof` is used to check for "window"', () => {
const sourceFile = ast(`
typeof window === 'undefined';
`);
const errors = Rule.prototype.apply(sourceFile);
const [error] = errors;
expect(errors.length).to.equal(1);
expect(error.getFailure()).to.include(`It's a bad idea to use 'typeof' to check for access to the 'window' API.`);
});
it('should not fail when `typeof` is used to check for some other variable', () => {
const sourceFile = ast(`
typeof foo === 'undefined';
`);
const errors = Rule.prototype.apply(sourceFile);
expect(errors.length).to.equal(0);
});
});
// Dependencies:
import { tsquery } from '@phenomnomnominal/tsquery';
import { RuleFailure, Rules } from 'tslint';
import { SourceFile } from 'typescript';
// Constants:
const BROWSER_GLOBALS = ['document', 'location', 'navigator', 'window'];
const BROWSER_GLOBAL_IDENTIFIER = `TypeOfExpression > Identifier[name=/${BROWSER_GLOBALS.join('|')}/]`;
const TYPEOF_EQUALS_UNDEFINED_QUERY = 'BinaryExpression:has(TypeOfExpression):has(StringLiteral[text="undefined"])';
const FAILURE_MESSAGE = (api: string) => `
It's a bad idea to use 'typeof' to check for access to the '${api}' API. Try to avoid using DOM APIs directly,
and instead use Angular's abstractions.
`;
export class Rule extends Rules.AbstractRule {
public apply(sourceFile: SourceFile): Array<RuleFailure> {
const potentialErrors = tsquery(sourceFile, TYPEOF_EQUALS_UNDEFINED_QUERY);
return potentialErrors.map(result => {
const [globalIdentifier] = tsquery(result, BROWSER_GLOBAL_IDENTIFIER);
if (globalIdentifier) {
return new RuleFailure(result.getSourceFile(), result.getStart(), result.getEnd(), FAILURE_MESSAGE(globalIdentifier.name as string), this.ruleName);
}
})
.filter(Boolean) as Array<RuleFailure>;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment