Skip to content

Commit

Permalink
feat: added support for regexp literal
Browse files Browse the repository at this point in the history
  • Loading branch information
DenyVeyten committed Jan 19, 2025
1 parent 506819b commit 8b588c9
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ export enum AstNodeTypes {
MemberExpression = "MemberExpression",
CallExpression = "CallExpression",
NewExpression = "NewExpression",
Literal = "Literal",
}
35 changes: 29 additions & 6 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/* eslint no-nested-ternary: off */
import browserslist from "browserslist";
import { AstNodeTypes, TargetNameMappings } from "./constants";
import {
AstMetadataApiWithTargetsResolver,
BrowserListConfig,
BrowsersListOpts,
Context,
ESLintNode,
HandleFailingRule,
SourceCode,
BrowserListConfig,
Target,
HandleFailingRule,
Context,
BrowsersListOpts,
} from "./types";
import { TargetNameMappings } from "./constants";

/*
3) Figures out which browsers user is targeting
Expand Down Expand Up @@ -109,8 +109,31 @@ export function lintExpressionStatement(
);
}

function checkRegexpLiteral(node: ESLintNode): boolean {
return (
node.type === AstNodeTypes.Literal &&
(!!node.regex || node.parent?.callee?.name === "RegExp")
);
}

export function lintLiteral(
context: Context,
handleFailingRule: HandleFailingRule,
rules: AstMetadataApiWithTargetsResolver[],
sourceCode: SourceCode,
node: ESLintNode
) {
const isRegexpLiteral = checkRegexpLiteral(node);
const failingRule = rules.find((rule) =>
rule.syntaxes?.some(
(syntax) => (isRegexpLiteral ? node.raw.includes(syntax) : false) // non-regexp literals are not supported yet
)
);
if (failingRule) handleFailingRule(failingRule, node);
}

function isStringLiteral(node: ESLintNode): boolean {
return node.type === "Literal" && typeof node.value === "string";
return node.type === AstNodeTypes.Literal && typeof node.value === "string";
}

function protoChainFromMemberExpression(node: ESLintNode): string[] {
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
import recommended from "./config/recommended";
import pkg from "../package.json";
import type { Linter } from "eslint";
import pkg from "../package.json";
import recommended from "./config/recommended";

//------------------------------------------------------------------------------
// Plugin Definition
Expand All @@ -32,7 +32,7 @@ const plugin = {

const configs = {
"flat/recommended": {
name: 'compat/flat/recommended',
name: "compat/flat/recommended",
plugins: { compat: plugin },
...recommended.flat,
} as Linter.FlatConfig,
Expand Down
9 changes: 8 additions & 1 deletion src/providers/caniuse-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as lite from "caniuse-lite";
import { STANDARD_TARGET_NAME_MAPPING, AstNodeTypes } from "../constants";
import { AstNodeTypes, STANDARD_TARGET_NAME_MAPPING } from "../constants";
import { AstMetadataApiWithTargetsResolver, Target } from "../types";

/**
Expand Down Expand Up @@ -241,6 +241,13 @@ const CanIUseProvider: Array<AstMetadataApiWithTargetsResolver> = [
astNodeType: AstNodeTypes.NewExpression,
object: "Float64Array",
},
{
caniuseId: "js-regexp-lookbehind",
astNodeType: AstNodeTypes.Literal,
name: "Lookbehind",
object: "RegExp",
syntaxes: ["?<=", "?<!"],
},
].map((rule) => ({
...rule,
getUnsupportedTargets,
Expand Down
29 changes: 21 additions & 8 deletions src/rules/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@
* Tells eslint to lint certain nodes (lintCallExpression, lintMemberExpression, lintNewExpression)
* Gets protochain for the ESLint nodes the plugin is interested in
*/
import fs from "fs";
import { Rule } from "eslint";
import findUp from "find-up";
import fs from "fs";
import memoize from "lodash.memoize";
import { Rule } from "eslint";
import {
determineTargetsFromConfig,
lintCallExpression,
lintExpressionStatement,
lintLiteral,
lintMemberExpression,
lintNewExpression,
lintExpressionStatement,
parseBrowsersListVersion,
determineTargetsFromConfig,
} from "../helpers"; // will be deprecated and introduced to this file
import { nodes } from "../providers";
import {
ESLintNode,
AstMetadataApiWithTargetsResolver,
BrowserListConfig,
HandleFailingRule,
Context,
BrowsersListOpts,
Context,
ESLintNode,
HandleFailingRule,
} from "../types";
import { nodes } from "../providers";

type ESLint = {
[astNodeTypeName: string]: (node: ESLintNode) => void;
Expand All @@ -45,6 +46,9 @@ function getName(node: ESLintNode): string {
case "CallExpression": {
return node.callee!.name;
}
case "Literal": {
return node.type;
}
default:
throw new Error("not found");
}
Expand Down Expand Up @@ -110,6 +114,7 @@ type RulesFilteredByTargets = {
NewExpression: AstMetadataApiWithTargetsResolver[];
MemberExpression: AstMetadataApiWithTargetsResolver[];
ExpressionStatement: AstMetadataApiWithTargetsResolver[];
Literal: AstMetadataApiWithTargetsResolver[];
};

/**
Expand All @@ -124,6 +129,7 @@ const getRulesForTargets = memoize(
NewExpression: [] as AstMetadataApiWithTargetsResolver[],
MemberExpression: [] as AstMetadataApiWithTargetsResolver[],
ExpressionStatement: [] as AstMetadataApiWithTargetsResolver[],
Literal: [] as AstMetadataApiWithTargetsResolver[],
};
const targets = JSON.parse(targetsJSON);

Expand Down Expand Up @@ -248,6 +254,13 @@ export default {
],
sourceCode
),
Literal: lintLiteral.bind(
null,
context,
handleFailingRule,
targetedRules.Literal,
sourceCode
),
// Keep track of all the defined variables. Do not report errors for nodes that are not defined
Identifier(node: ESLintNode) {
if (node.parent) {
Expand Down
14 changes: 12 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { APIKind } from "ast-metadata-inferer/lib/types";
import type { Options as DefaultBrowsersListOpts } from "browserslist";
import { Rule } from "eslint";
import { TargetNameMappings } from "./constants";
import type { Options as DefaultBrowsersListOpts } from "browserslist";

export type BrowserListConfig =
| string
Expand All @@ -18,8 +18,13 @@ type AstMetadataApi = {
type?: string;
name?: string;
object: string;
astNodeType: "MemberExpression" | "CallExpression" | "NewExpression";
astNodeType:
| "MemberExpression"
| "CallExpression"
| "NewExpression"
| "Literal";
property?: string;
syntaxes?: string[];
protoChainId: string;
protoChain: Array<string>;
};
Expand Down Expand Up @@ -49,6 +54,11 @@ export type ESLintNode = {
name: string;
type?: string;
};
regex?: {
flags: string;
pattern: string;
};
raw: string;
};

export type SourceCode = import("eslint").SourceCode;
Expand Down
18 changes: 17 additions & 1 deletion test/e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RuleTester } from "eslint";
import rule from "../src/rules/compat";
import { parser } from "typescript-eslint";
import rule from "../src/rules/compat";

const ruleTester = new RuleTester({
languageOptions: {
Expand Down Expand Up @@ -691,5 +691,21 @@ ruleTester.run("compat", rule, {
},
],
},
{
code: "/(?<=y)x/, new RegExp('(?<!y)x'), 'x', true, false, null, 1, 0n",
settings: {
browsers: ["Safari >= 16.3", "iOS >= 16.3"],
},
errors: [
{
message:
"Lookbehind is not supported in Safari 16.3, iOS Safari 16.3",
},
{
message:
"Lookbehind is not supported in Safari 16.3, iOS Safari 16.3",
},
],
},
],
});

0 comments on commit 8b588c9

Please sign in to comment.