Skip to content
Open
Show file tree
Hide file tree
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
39 changes: 37 additions & 2 deletions src/get-static-value.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,10 @@ function isEffectivelyConst(variable) {
* Checks if a variable has mutation in its property.
* @param {Variable} variable The variable to check.
* @param {Scope|null} initialScope The scope to start finding variable. Optional. If the node is a computed property node and this scope was given, this checks the computed property name by the `getStringIfConstant` function with the scope, and returns the value of it.
* @param {object} initialValue The initial value of the variable.
* @returns {boolean} True if the variable has mutation in its property.
*/
function hasMutationInProperty(variable, initialScope) {
function hasMutationInProperty(variable, initialScope, initialValue) {
for (const ref of variable.references) {
let node = /** @type {TSESTreeNode} */ (ref.identifier)
while (node && node.parent && node.parent.type === "MemberExpression") {
Expand Down Expand Up @@ -349,6 +350,14 @@ function hasMutationInProperty(variable, initialScope) {
// This is a mutation.
return true
}
// Exclude nested receivers such as `set.foo.add()`.
if (
node.object === ref.identifier &&
isNameOfMutationCollectionMethod(methodName, initialValue)
) {
// This is a mutation.
return true
}
}
}
return false
Expand All @@ -375,6 +384,26 @@ function hasMutationInProperty(variable, initialScope) {
name === "unshift"
)
}

/**
* Checks if a method name is one of the mutation methods of the given collection.
* @param {StaticValue|null} methodName The method name to check.
* @param {object} collection The collection to check.
* @returns {boolean} True if the method name is a mutation method of the collection.
*/
function isNameOfMutationCollectionMethod(methodName, collection) {
if (methodName == null || methodName.value == null) {
return false
}
const name = methodName.value
if (collection instanceof Set) {
return name === "add" || name === "delete" || name === "clear"
}
if (collection instanceof Map) {
return name === "set" || name === "delete" || name === "clear"
}
return false
}
}

/**
Expand Down Expand Up @@ -628,7 +657,13 @@ const operations = Object.freeze({
typeof init.value === "object" &&
init.value !== null
) {
if (hasMutationInProperty(variable, initialScope)) {
if (
hasMutationInProperty(
variable,
initialScope,
init.value,
)
) {
// This variable has mutation in its property.
return null
}
Expand Down
52 changes: 52 additions & 0 deletions test/get-static-value.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,58 @@ const aMap = Object.freeze({
code: "const a = ['a']; a.unshift('b'); a.join()",
expected: null,
},
{
code: "const a = new Set(); a.add('a'); a.size",
expected: null,
},
{
code: "const a = new Set(['a']); a.delete('a'); a.size",
expected: null,
},
{
code: "const a = new Set(['a']); a.clear(); a.size",
expected: null,
},
{
code: "const a = new Map(); a.set('a', 1); a.size",
expected: null,
},
{
code: "const a = new Map([['a', 1]]); a.delete('a'); a.size",
expected: null,
},
{
code: "const a = new Map([['a', 1]]); a.clear(); a.size",
expected: null,
},
{
code: "const method = 'add'; const a = new Set(); a[method]('a'); a.size",
expected: null,
},
{
code: "const a = new Set(['a']); a.has('a')",
expected: { value: true },
},
{
code: "const a = {value: 1, add: String}; a.add(2); a.value",
expected: { value: 1 },
},
{
code: "const a = {value: 1, clear: String}; a.clear(); a.value",
expected: { value: 1 },
},
{
code: "const a = {value: 1, delete: String}; a.delete(2); a.value",
expected: { value: 1 },
},
{
code: "const a = {value: 1, set: String}; a.set(2); a.value",
expected: { value: 1 },
},
{
code: `const a = {value: 1, set: String}; a["s" + "et"](2); a.value`,
expected: { value: 1 },
},
{
code: "const a = {foo: ['a']}; a.foo.shift(); a",
expected: null,
Expand Down