Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Commit

Permalink
convert no-unsupported-browser-code rule to use a walk function (#795)
Browse files Browse the repository at this point in the history
  • Loading branch information
drexler authored and Josh Goldberg committed Jan 24, 2019
1 parent f67d898 commit bdd8043
Showing 1 changed file with 73 additions and 69 deletions.
142 changes: 73 additions & 69 deletions src/noUnsupportedBrowserCodeRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ interface BrowserVersion {
version: number | string;
}

interface Options {
readonly supportedBrowsers: { [key: string]: BrowserVersion };
}

export class Rule extends Lint.Rules.AbstractRule {
public static metadata: ExtendedMetadata = {
ruleName: 'no-unsupported-browser-code',
Expand All @@ -32,85 +36,66 @@ export class Rule extends Lint.Rules.AbstractRule {
};

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NoUnsupportedBrowserCodeRuleWalker(sourceFile, this.getOptions()));
}
}

class NoUnsupportedBrowserCodeRuleWalker extends Lint.RuleWalker {
private readonly supportedBrowsers: { [key: string]: BrowserVersion };

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);
this.supportedBrowsers = this.parseSupportedBrowsers();
}

protected visitSourceFile(node: ts.SourceFile): void {
// do not call super.visitSourceFile because we're already scanning the full file below

forEachTokenWithTrivia(node, (text, tokenSyntaxKind, range) => {
let regex;
if (tokenSyntaxKind === ts.SyntaxKind.MultiLineCommentTrivia) {
regex = new RegExp(`${JSDOC_BROWSERSPECIFIC}\\s*(.*)`, 'gi');
} else if (tokenSyntaxKind === ts.SyntaxKind.SingleLineCommentTrivia) {
regex = new RegExp(`${COMMENT_BROWSERSPECIFIC}\\s*(.*)`, 'gi');
} else {
return;
}

let match;
const tokenText = text.substring(range.pos, range.end);
// tslint:disable-next-line:no-conditional-assignment
while ((match = regex.exec(tokenText))) {
const browser = this.parseBrowserString(match[1]);
if (browser === undefined) {
break;
}

this.findUnsupportedBrowserFailures(browser, range.pos, range.end - range.pos);
}
});
return this.applyWithFunction(sourceFile, walk, this.parseSupportedBrowsers(this.getOptions()));
}

private parseBrowserString(browser: string): BrowserVersion | undefined {
// This case-insensitive regex contains 3 capture groups:
// #1 looks for a browser name (combination of spaces and alpha)
// #2 looks for an optional comparison operator (>=, <=, etc)
// #3 looks for a version number
const regex = /([a-zA-Z ]*)(>=|<=|<|>)?\s*(\d*)/i;
const match = browser.match(regex);
if (match === null) {
return undefined;
}

return {
name: match[1].trim(),
comparison: match[2] || '=',
version: parseInt(match[3], 10) || UNSPECIFIED_BROWSER_VERSION
};
}

private parseSupportedBrowsers(): { [key: string]: BrowserVersion } {
private parseSupportedBrowsers(options: Lint.IOptions): Options {
const result: { [key: string]: BrowserVersion } = {};

this.getOptions().forEach((option: unknown) => {
const ruleArguments: unknown[] = options.ruleArguments;

(ruleArguments || []).forEach((option: unknown) => {
if (option instanceof Array) {
option.forEach((browserString: string) => {
const browser = this.parseBrowserString(browserString);
const browser = parseBrowserString(browserString);
if (browser !== undefined) {
result[browser.name.toLowerCase()] = browser;
}
});
}
});
return result;

return {
supportedBrowsers: result
};
}
}

function parseBrowserString(browser: string): BrowserVersion | undefined {
// This case-insensitive regex contains 3 capture groups:
// #1 looks for a browser name (combination of spaces and alpha)
// #2 looks for an optional comparison operator (>=, <=, etc)
// #3 looks for a version number
const regex = /([a-zA-Z ]*)(>=|<=|<|>)?\s*(\d*)/i;
const match = browser.match(regex);
if (match === null) {
return undefined;
}

return {
name: match[1].trim(),
comparison: match[2] || '=',
version: parseInt(match[3], 10) || UNSPECIFIED_BROWSER_VERSION
};
}

function walk(ctx: Lint.WalkContext<Options>) {
const { supportedBrowsers } = ctx.options;

function findUnsupportedBrowserFailures(targetBrowser: BrowserVersion, startPos: number, length: number) {
if (!isSupportedBrowser(targetBrowser)) {
ctx.addFailureAt(startPos, length, `${FAILURE_BROWSER_STRING}: ${targetBrowser.name}`);
} else if (!isSupportedBrowserVersion(targetBrowser)) {
ctx.addFailureAt(startPos, length, `${FAILURE_VERSION_STRING}: ${targetBrowser.name} ${targetBrowser.version}`);
}
}

private isSupportedBrowser(targetBrowser: BrowserVersion): boolean {
return targetBrowser.name.toLowerCase() in this.supportedBrowsers;
function isSupportedBrowser(targetBrowser: BrowserVersion): boolean {
return targetBrowser.name.toLowerCase() in supportedBrowsers;
}

private isSupportedBrowserVersion(targetBrowser: BrowserVersion): boolean {
const supportedBrowser = this.supportedBrowsers[targetBrowser.name.toLowerCase()];
function isSupportedBrowserVersion(targetBrowser: BrowserVersion): boolean {
const supportedBrowser = supportedBrowsers[targetBrowser.name.toLowerCase()];

if (supportedBrowser.version === UNSPECIFIED_BROWSER_VERSION) {
// If the supplied browser supports every version (aka unspecified
Expand All @@ -134,11 +119,30 @@ class NoUnsupportedBrowserCodeRuleWalker extends Lint.RuleWalker {
}
}

private findUnsupportedBrowserFailures(targetBrowser: BrowserVersion, startPos: number, length: number) {
if (!this.isSupportedBrowser(targetBrowser)) {
this.addFailureAt(startPos, length, `${FAILURE_BROWSER_STRING}: ${targetBrowser.name}`);
} else if (!this.isSupportedBrowserVersion(targetBrowser)) {
this.addFailureAt(startPos, length, `${FAILURE_VERSION_STRING}: ${targetBrowser.name} ${targetBrowser.version}`);
}
function cb(node: ts.Node): void {
forEachTokenWithTrivia(node, (text, tokenSyntaxKind, range) => {
let regex;
if (tokenSyntaxKind === ts.SyntaxKind.MultiLineCommentTrivia) {
regex = new RegExp(`${JSDOC_BROWSERSPECIFIC}\\s*(.*)`, 'gi');
} else if (tokenSyntaxKind === ts.SyntaxKind.SingleLineCommentTrivia) {
regex = new RegExp(`${COMMENT_BROWSERSPECIFIC}\\s*(.*)`, 'gi');
} else {
return;
}

let match;
const tokenText = text.substring(range.pos, range.end);
// tslint:disable-next-line:no-conditional-assignment
while ((match = regex.exec(tokenText))) {
const browser = parseBrowserString(match[1]);
if (browser === undefined) {
break;
}

findUnsupportedBrowserFailures(browser, range.pos, range.end - range.pos);
}
});
}

return ts.forEachChild(ctx.sourceFile, cb);
}

0 comments on commit bdd8043

Please sign in to comment.