Navigation Menu

Skip to content

Commit

Permalink
Update: Handle locally unsupported regex in computed property keys (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mdjermanovic authored and mysticatea committed Dec 25, 2019
1 parent 4744397 commit 3fa39a6
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 18 deletions.
59 changes: 43 additions & 16 deletions lib/rules/utils/ast-utils.js
Expand Up @@ -865,6 +865,44 @@ module.exports = {
return isFunction(node) && module.exports.isEmptyBlock(node.body);
},

/**
* Returns the result of the string conversion applied to the evaluated value of the given expression node,
* if it can be determined statically.
*
* This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
* In all other cases, this function returns `null`.
* @param {ASTNode} node Expression node.
* @returns {string|null} String value if it can be determined. Otherwise, `null`.
*/
getStaticStringValue(node) {
switch (node.type) {
case "Literal":
if (node.value === null) {
if (module.exports.isNullLiteral(node)) {
return String(node.value); // "null"
}
if (node.regex) {
return `/${node.regex.pattern}/${node.regex.flags}`;
}

// Otherwise, this is an unknown literal. The function will return null.

} else {
return String(node.value);
}
break;
case "TemplateLiteral":
if (node.expressions.length === 0 && node.quasis.length === 1) {
return node.quasis[0].value.cooked;
}
break;

// no default
}

return null;
},

/**
* Gets the property name of a given node.
* The node can be a MemberExpression, a Property, or a MethodDefinition.
Expand Down Expand Up @@ -911,23 +949,12 @@ module.exports = {
// no default
}

switch (prop && prop.type) {
case "Literal":
return String(prop.value);

case "TemplateLiteral":
if (prop.expressions.length === 0 && prop.quasis.length === 1) {
return prop.quasis[0].value.cooked;
}
break;

case "Identifier":
if (!node.computed) {
return prop.name;
}
break;
if (prop) {
if (prop.type === "Identifier" && !node.computed) {
return prop.name;
}

// no default
return module.exports.getStaticStringValue(prop);
}

return null;
Expand Down
4 changes: 3 additions & 1 deletion tests/lib/rules/no-dupe-keys.js
Expand Up @@ -32,6 +32,7 @@ ruleTester.run("no-dupe-keys", rule, {
{ code: "var x = { a: b, ...c }", parserOptions: { ecmaVersion: 2018 } },
{ code: "var x = { get a() {}, set a (value) {} };", parserOptions: { ecmaVersion: 6 } },
{ code: "var x = { a: 1, b: { a: 2 } };", parserOptions: { ecmaVersion: 6 } },
{ code: "var x = ({ null: 1, [/(?<zero>0)/]: 2 })", parserOptions: { ecmaVersion: 2018 } },
{ code: "var {a, a} = obj", parserOptions: { ecmaVersion: 6 } }
],
invalid: [
Expand All @@ -44,6 +45,7 @@ ruleTester.run("no-dupe-keys", rule, {
{ code: "var foo = {\n bar: 1,\n bar: 1,\n}", errors: [{ messageId: "unexpected", data: { name: "bar" }, line: 3, column: 3 }] },
{ code: "var x = { a: 1, get a() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
{ code: "var x = { a: 1, set a(value) {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
{ code: "var x = { a: 1, b: { a: 2 }, get b() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "b" }, type: "ObjectExpression" }] }
{ code: "var x = { a: 1, b: { a: 2 }, get b() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "b" }, type: "ObjectExpression" }] },
{ code: "var x = ({ '/(?<zero>0)/': 1, [/(?<zero>0)/]: 2 })", parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "unexpected", data: { name: "/(?<zero>0)/" }, type: "ObjectExpression" }] }
]
});
11 changes: 11 additions & 0 deletions tests/lib/rules/no-restricted-properties.js
Expand Up @@ -95,6 +95,12 @@ ruleTester.run("no-restricted-properties", rule, {
options: [{
object: "foo"
}]
}, {
code: "foo[/(?<zero>0)/]",
options: [{
property: "null"
}],
parserOptions: { ecmaVersion: 2018 }
}, {
code: "let bar = foo;",
options: [{ object: "foo", property: "bar" }],
Expand Down Expand Up @@ -248,6 +254,11 @@ ruleTester.run("no-restricted-properties", rule, {
code: "foo.bar.baz();",
options: [{ property: "bar" }],
errors: [{ message: "'bar' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "foo[/(?<zero>0)/]",
options: [{ property: "/(?<zero>0)/" }],
parserOptions: { ecmaVersion: 2018 },
errors: [{ message: "'/(?<zero>0)/' is restricted from being used.", type: "MemberExpression" }]
}, {
code: "require.call({}, 'foo')",
options: [{
Expand Down
4 changes: 3 additions & 1 deletion tests/lib/rules/no-self-assign.js
Expand Up @@ -50,6 +50,7 @@ ruleTester.run("no-self-assign", rule, {
{ code: "a[b] = a.b", options: [{ props: true }] },
{ code: "a.b().c = a.b().c", options: [{ props: true }] },
{ code: "b().c = b().c", options: [{ props: true }] },
{ code: "a.null = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 } },
{ code: "a[b + 1] = a[b + 1]", options: [{ props: true }] }, // it ignores non-simple computed properties.
{
code: "a.b = a.b",
Expand Down Expand Up @@ -133,6 +134,7 @@ ruleTester.run("no-self-assign", rule, {
code: "this.x = this.x",
options: [{ props: true }],
errors: ["'this.x' is assigned to itself."]
}
},
{ code: "a['/(?<zero>0)/'] = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: ["'a[/(?<zero>0)/]' is assigned to itself."] }
]
});
6 changes: 6 additions & 0 deletions tests/lib/rules/sort-keys.js
Expand Up @@ -33,6 +33,7 @@ ruleTester.run("sort-keys", rule, {
{ code: "var obj = {$:1, A:3, _:2, a:4}", options: [] },
{ code: "var obj = {1:1, '11':2, 2:4, A:3}", options: [] },
{ code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: [] },
{ code: "var obj = { [/(?<zero>0)/]: 1, '/(?<zero>0)/': 2 }", options: [], parserOptions: { ecmaVersion: 2018 } },

// ignore non-simple computed properties.
{ code: "var obj = {a:1, b:3, [a + b]: -1, c:2}", options: [], parserOptions: { ecmaVersion: 6 } },
Expand Down Expand Up @@ -204,6 +205,11 @@ ruleTester.run("sort-keys", rule, {
code: "var obj = {'#':1, À:3, 'Z':2, è:4}",
errors: ["Expected object keys to be in ascending order. 'Z' should be before 'À'."]
},
{
code: "var obj = { null: 1, [/(?<zero>0)/]: 2 }",
parserOptions: { ecmaVersion: 2018 },
errors: ["Expected object keys to be in ascending order. '/(?<zero>0)/' should be before 'null'."]
},

// not ignore properties not separated by spread properties
{
Expand Down
84 changes: 84 additions & 0 deletions tests/lib/rules/utils/ast-utils.js
Expand Up @@ -403,6 +403,83 @@ describe("ast-utils", () => {
});
});

describe("getStaticStringValue", () => {

/* eslint-disable quote-props */
const expectedResults = {

// string literals
"''": "",
"'foo'": "foo",

// boolean literals
"false": "false",
"true": "true",

// null literal
"null": "null",

// number literals
"0": "0",
"0.": "0",
".0": "0",
"1": "1",
"1.": "1",
".1": "0.1",
"12": "12",
".12": "0.12",
"0.12": "0.12",
"12.34": "12.34",
"12e3": "12000",
"12e-3": "0.012",
"12.34e5": "1234000",
"12.34e-5": "0.0001234",
"011": "9",
"081": "81",
"0b11": "3",
"0b011": "3",
"0o11": "9",
"0o011": "9",
"0x11": "17",
"0x011": "17",

// regexp literals
"/a/": "/a/",
"/a/i": "/a/i",
"/[0-9]/": "/[0-9]/",
"/(?<zero>0)/": "/(?<zero>0)/",
"/(?<zero>0)/s": "/(?<zero>0)/s",
"/(?<=a)b/s": "/(?<=a)b/s",

// simple template literals
"``": "",
"`foo`": "foo",

// unsupported
"`${''}`": null,
"`${0}`": null,
"tag``": null,
"-0": null,
"-1": null,
"1 + 2": null,
"[]": null,
"({})": null,
"foo": null,
"undefined": null,
"this": null,
"(function () {})": null
};
/* eslint-enable quote-props */

Object.keys(expectedResults).forEach(key => {
it(`should return ${expectedResults[key]} for ${key}`, () => {
const ast = espree.parse(key, { ecmaVersion: 2018 });

assert.strictEqual(astUtils.getStaticStringValue(ast.body[0].expression), expectedResults[key]);
});
});
});

describe("getStaticPropertyName", () => {
it("should return 'b' for `a.b`", () => {
const ast = espree.parse("a.b");
Expand Down Expand Up @@ -509,6 +586,13 @@ describe("ast-utils", () => {
assert.strictEqual(astUtils.getStaticPropertyName(node), "100");
});

it("should return '/(?<zero>0)/' for `[/(?<zero>0)/]: 1`", () => {
const ast = espree.parse("({[/(?<zero>0)/]: 1})", { ecmaVersion: 2018 });
const node = ast.body[0].expression.properties[0];

assert.strictEqual(astUtils.getStaticPropertyName(node), "/(?<zero>0)/");
});

it("should return null for `[b]: 1`", () => {
const ast = espree.parse("({[b]: 1})", { ecmaVersion: 6 });
const node = ast.body[0].expression.properties[0];
Expand Down
17 changes: 17 additions & 0 deletions tests/lib/rules/yoda.js
Expand Up @@ -91,6 +91,10 @@ ruleTester.run("yoda", rule, {
}, {
code: "if (0 <= a.b && a[\"b\"] <= 100) {}",
options: ["never", { exceptRange: true }]
}, {
code: "if (1 <= a['/(?<zero>0)/'] && a[/(?<zero>0)/] <= 100) {}",
options: ["never", { exceptRange: true }],
parserOptions: { ecmaVersion: 2018 }
},

// onlyEquality
Expand Down Expand Up @@ -412,6 +416,19 @@ ruleTester.run("yoda", rule, {
}
]
},
{
code: "if (0 <= a.null && a[/(?<zero>0)/] <= 1) {}",
output: "if (a.null >= 0 && a[/(?<zero>0)/] <= 1) {}",
options: ["never", { exceptRange: true }],
parserOptions: { ecmaVersion: 2018 },
errors: [
{
messageId: "expected",
data: { expectedSide: "right", operator: "<=" },
type: "BinaryExpression"
}
]
},
{
code: "if (3 == a) {}",
output: "if (a == 3) {}",
Expand Down

0 comments on commit 3fa39a6

Please sign in to comment.