Skip to content

Commit 7bc6c71

Browse files
jtbandesTanujkanti4441kirkwaiblingersnitin315
authored
feat: add no-unassigned-vars rule (#19618)
* feat: add no-unassigned-vars rule * Apply suggestions from code review Co-authored-by: Tanuj Kanti <[email protected]> Co-authored-by: Kirk Waiblinger <[email protected]> * add typescript examples * Update docs/src/rules/no-unassigned-vars.md Co-authored-by: Nitin Kumar <[email protected]> * review feedback * more review feedback * Apply suggestions from code review Co-authored-by: Tanuj Kanti <[email protected]> * npm run build:site * fix missing directive * review feedback * review feedback * add no-unused-vars to related_rules and add back-links from all related rules * review feedback * add When Not To Use It per @snitin315 feedback --------- Co-authored-by: Tanuj Kanti <[email protected]> Co-authored-by: Kirk Waiblinger <[email protected]> Co-authored-by: Nitin Kumar <[email protected]>
1 parent ee40364 commit 7bc6c71

12 files changed

Lines changed: 427 additions & 0 deletions

File tree

docs/src/_data/rules.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@
337337
"frozen": false,
338338
"hasSuggestions": false
339339
},
340+
{
341+
"name": "no-unassigned-vars",
342+
"description": "Disallow `let` or `var` variables that are read but never assigned",
343+
"recommended": false,
344+
"fixable": false,
345+
"frozen": false,
346+
"hasSuggestions": false
347+
},
340348
{
341349
"name": "no-undef",
342350
"description": "Disallow the use of undeclared variables unless mentioned in `/*global */` comments",

docs/src/_data/rules_meta.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3260,6 +3260,19 @@
32603260
},
32613261
"fixable": "whitespace"
32623262
},
3263+
"no-unassigned-vars": {
3264+
"type": "problem",
3265+
"dialects": [
3266+
"typescript",
3267+
"javascript"
3268+
],
3269+
"language": "javascript",
3270+
"docs": {
3271+
"description": "Disallow `let` or `var` variables that are read but never assigned",
3272+
"recommended": false,
3273+
"url": "https://eslint.org/docs/latest/rules/no-unassigned-vars"
3274+
}
3275+
},
32633276
"no-undef": {
32643277
"type": "problem",
32653278
"defaultOptions": [

docs/src/rules/init-declarations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
title: init-declarations
33
rule_type: suggestion
4+
related_rules:
5+
- no-unassigned-vars
46
---
57

68

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
---
2+
title: no-unassigned-vars
3+
rule_type: problem
4+
related_rules:
5+
- init-declarations
6+
- no-unused-vars
7+
- prefer-const
8+
---
9+
10+
11+
This rule flags `let` or `var` declarations that are never assigned a value but are still read or used in the code. Since these variables will always be `undefined`, their usage is likely a programming mistake.
12+
13+
For example, if you check the value of a `status` variable, but it was never given a value, it will always be `undefined`:
14+
15+
```js
16+
let status;
17+
18+
// ...forgot to assign a value to status...
19+
20+
if (status === 'ready') {
21+
console.log('Ready!');
22+
}
23+
```
24+
25+
## Rule Details
26+
27+
Examples of **incorrect** code for this rule:
28+
29+
::: incorrect
30+
31+
```js
32+
/*eslint no-unassigned-vars: "error"*/
33+
34+
let status;
35+
if (status === 'ready') {
36+
console.log('Ready!');
37+
}
38+
39+
let user;
40+
greet(user);
41+
42+
function test() {
43+
let error;
44+
return error || "Unknown error";
45+
}
46+
47+
let options;
48+
const { debug } = options || {};
49+
50+
let flag;
51+
while (!flag) {
52+
// Do something...
53+
}
54+
55+
let config;
56+
function init() {
57+
return config?.enabled;
58+
}
59+
```
60+
61+
:::
62+
63+
In TypeScript:
64+
65+
::: incorrect
66+
67+
```ts
68+
/*eslint no-unassigned-vars: "error"*/
69+
70+
let value: number | undefined;
71+
console.log(value);
72+
```
73+
74+
:::
75+
76+
Examples of **correct** code for this rule:
77+
78+
::: correct
79+
80+
```js
81+
/*eslint no-unassigned-vars: "error"*/
82+
83+
let message = "hello";
84+
console.log(message);
85+
86+
let user;
87+
user = getUser();
88+
console.log(user.name);
89+
90+
let count;
91+
count = 1;
92+
count++;
93+
94+
// Variable is unused (should be reported by `no-unused-vars` only)
95+
let temp;
96+
97+
let error;
98+
if (somethingWentWrong) {
99+
error = "Something went wrong";
100+
}
101+
console.log(error);
102+
103+
let item;
104+
for (item of items) {
105+
process(item);
106+
}
107+
108+
let config;
109+
function setup() {
110+
config = { debug: true };
111+
}
112+
setup();
113+
console.log(config);
114+
115+
let one = undefined;
116+
if (one === two) {
117+
// Noop
118+
}
119+
```
120+
121+
:::
122+
123+
In TypeScript:
124+
125+
::: correct
126+
127+
```ts
128+
/*eslint no-unassigned-vars: "error"*/
129+
130+
declare let value: number | undefined;
131+
console.log(value);
132+
```
133+
134+
:::
135+
136+
## When Not To Use It
137+
138+
You can disable this rule if your code intentionally uses variables that are declared and used, but are never assigned a value. This might be the case in:
139+
140+
- Legacy codebases where uninitialized variables are used as placeholders.
141+
- Certain TypeScript use cases where variables are declared with a type and intentionally left unassigned (though using `declare` is preferred).

docs/src/rules/no-unused-vars.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: no-unused-vars
33
rule_type: problem
44
related_rules:
5+
- no-unassigned-vars
56
- no-useless-assignment
67
---
78

docs/src/rules/prefer-const.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: prefer-const
33
rule_type: suggestion
44
related_rules:
55
- no-var
6+
- no-unassigned-vars
67
- no-use-before-define
78
---
89

lib/rules/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ module.exports = new LazyLoadingRuleMap(
225225
"no-this-before-super": () => require("./no-this-before-super"),
226226
"no-throw-literal": () => require("./no-throw-literal"),
227227
"no-trailing-spaces": () => require("./no-trailing-spaces"),
228+
"no-unassigned-vars": () => require("./no-unassigned-vars"),
228229
"no-undef": () => require("./no-undef"),
229230
"no-undef-init": () => require("./no-undef-init"),
230231
"no-undefined": () => require("./no-undefined"),

lib/rules/no-unassigned-vars.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @fileoverview Rule to flag variables that are never assigned
3+
* @author Jacob Bandes-Storch <https://github.com/jtbandes>
4+
*/
5+
"use strict";
6+
7+
//------------------------------------------------------------------------------
8+
// Rule Definition
9+
//------------------------------------------------------------------------------
10+
11+
/** @type {import('../types').Rule.RuleModule} */
12+
module.exports = {
13+
meta: {
14+
type: "problem",
15+
dialects: ["typescript", "javascript"],
16+
language: "javascript",
17+
18+
docs: {
19+
description:
20+
"Disallow `let` or `var` variables that are read but never assigned",
21+
recommended: false,
22+
url: "https://eslint.org/docs/latest/rules/no-unassigned-vars",
23+
},
24+
25+
schema: [],
26+
messages: {
27+
unassigned:
28+
"'{{name}}' is always 'undefined' because it's never assigned.",
29+
},
30+
},
31+
32+
create(context) {
33+
const sourceCode = context.sourceCode;
34+
35+
return {
36+
VariableDeclarator(node) {
37+
/** @type {import('estree').VariableDeclaration} */
38+
const declaration = node.parent;
39+
const shouldCheck =
40+
!node.init &&
41+
node.id.type === "Identifier" &&
42+
declaration.kind !== "const" &&
43+
!declaration.declare;
44+
if (!shouldCheck) {
45+
return;
46+
}
47+
const [variable] = sourceCode.getDeclaredVariables(node);
48+
if (!variable) {
49+
return;
50+
}
51+
let hasRead = false;
52+
for (const reference of variable.references) {
53+
if (reference.isWrite()) {
54+
return;
55+
}
56+
if (reference.isRead()) {
57+
hasRead = true;
58+
}
59+
}
60+
if (!hasRead) {
61+
// Variables that are never read should be flagged by no-unused-vars instead
62+
return;
63+
}
64+
context.report({
65+
node,
66+
messageId: "unassigned",
67+
data: { name: node.id.name },
68+
});
69+
},
70+
};
71+
},
72+
};

lib/types/rules.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,6 +3714,13 @@ export interface ESLintRules extends Linter.RulesRecord {
37143714
]
37153715
>;
37163716

3717+
/**
3718+
* Rule to disallow `let` or `var` variables that are read but never assigned.
3719+
*
3720+
* @see https://eslint.org/docs/latest/rules/no-unassigned-vars
3721+
*/
3722+
"no-unassigned-vars": Linter.RuleEntry<[]>;
3723+
37173724
/**
37183725
* Rule to disallow the use of undeclared variables unless mentioned in \/*global *\/ comments.
37193726
*

packages/eslint-config-eslint/base.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const jsConfigs = [
8888
"no-sequences": "error",
8989
"no-shadow": "error",
9090
"no-throw-literal": "error",
91+
"no-unassigned-vars": "error",
9192
"no-undef": ["error", { typeof: true }],
9293
"no-undef-init": "error",
9394
"no-undefined": "error",

0 commit comments

Comments
 (0)