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

convert no-unsupported-browser-code rule to use a walk function #795

Merged
merged 1 commit into from
Jan 24, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}