Created
August 8, 2018 00:15
-
-
Save phenomnomnominal/6d4fbb6ca676214e81c1bc2f65dcf78e to your computer and use it in GitHub Desktop.
TSLint rule with TSQuery
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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