Skip to content

Commit 6cc6d15

Browse files
committed
feat: port rule no-var
1 parent 1e5a9c6 commit 6cc6d15

File tree

8 files changed

+238
-1
lines changed

8 files changed

+238
-1
lines changed

internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ import (
123123
"github.com/web-infra-dev/rslint/internal/rules/no_loss_of_precision"
124124
"github.com/web-infra-dev/rslint/internal/rules/no_sparse_arrays"
125125
"github.com/web-infra-dev/rslint/internal/rules/no_template_curly_in_string"
126+
"github.com/web-infra-dev/rslint/internal/rules/no_var"
126127
)
127128

128129
// RslintConfig represents the top-level configuration array
@@ -422,6 +423,7 @@ func registerAllCoreEslintRules() {
422423
GlobalRuleRegistry.Register("no-loss-of-precision", no_loss_of_precision.NoLossOfPrecisionRule)
423424
GlobalRuleRegistry.Register("no-template-curly-in-string", no_template_curly_in_string.NoTemplateCurlyInString)
424425
GlobalRuleRegistry.Register("no-sparse-arrays", no_sparse_arrays.NoSparseArraysRule)
426+
GlobalRuleRegistry.Register("no-var", no_var.NoVarRule)
425427
}
426428

427429
// getAllTypeScriptEslintPluginRules returns all rules from the global registry.

internal/rules/no_var/no_var.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package no_var
2+
3+
import (
4+
"github.com/microsoft/typescript-go/shim/ast"
5+
"github.com/web-infra-dev/rslint/internal/rule"
6+
)
7+
8+
// https://eslint.org/docs/latest/rules/no-var
9+
var NoVarRule = rule.Rule{
10+
Name: "no-var",
11+
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
12+
return rule.RuleListeners{
13+
ast.KindVariableDeclarationList: func(node *ast.Node) {
14+
// BlockScoped = Let | Const | Using | AwaitUsing
15+
// If none of those flags are set, it's a var declaration.
16+
if node.Flags&ast.NodeFlagsBlockScoped != 0 {
17+
return
18+
}
19+
20+
// Skip var inside `declare global { var ... }` (TypeScript ambient context)
21+
if isInDeclareGlobal(node) {
22+
return
23+
}
24+
25+
// Report on the VariableStatement parent if it exists (for standalone var),
26+
// otherwise report on the VariableDeclarationList itself (for-loop initializer).
27+
reportNode := node
28+
if node.Parent != nil && node.Parent.Kind == ast.KindVariableStatement {
29+
reportNode = node.Parent
30+
}
31+
32+
ctx.ReportNode(reportNode, rule.RuleMessage{
33+
Id: "unexpectedVar",
34+
Description: "Unexpected var, use let or const instead.",
35+
})
36+
},
37+
}
38+
},
39+
}
40+
41+
// isInDeclareGlobal checks if a node is inside a `declare global { }` block.
42+
func isInDeclareGlobal(node *ast.Node) bool {
43+
current := node.Parent
44+
for current != nil {
45+
if current.Kind == ast.KindModuleBlock {
46+
// Check if the parent ModuleDeclaration is `declare global`
47+
parent := current.Parent
48+
if parent != nil && parent.Kind == ast.KindModuleDeclaration {
49+
modDecl := parent.AsModuleDeclaration()
50+
if modDecl != nil && modDecl.Name() != nil &&
51+
modDecl.Name().Kind == ast.KindGlobalKeyword {
52+
return true
53+
}
54+
}
55+
}
56+
current = current.Parent
57+
}
58+
return false
59+
}

internal/rules/no_var/no_var.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# no-var
2+
3+
## Rule Details
4+
5+
Requires `let` or `const` instead of `var`. ECMAScript 6 introduced `let` and `const` as alternatives to `var` for variable declarations. `let` and `const` provide block scoping, which helps avoid common issues caused by the function scoping of `var`.
6+
7+
Examples of **incorrect** code for this rule:
8+
9+
```javascript
10+
var x = 'y';
11+
var CONFIG = {};
12+
```
13+
14+
Examples of **correct** code for this rule:
15+
16+
```javascript
17+
let x = 'y';
18+
const CONFIG = {};
19+
```
20+
21+
## Original Documentation
22+
23+
- [ESLint no-var](https://eslint.org/docs/latest/rules/no-var)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package no_var
2+
3+
import (
4+
"testing"
5+
6+
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures"
7+
"github.com/web-infra-dev/rslint/internal/rule_tester"
8+
)
9+
10+
func TestNoVarRule(t *testing.T) {
11+
rule_tester.RunRuleTester(
12+
fixtures.GetRootDir(),
13+
"tsconfig.json",
14+
t,
15+
&NoVarRule,
16+
// Valid cases - ported from ESLint
17+
[]rule_tester.ValidTestCase{
18+
{Code: `const JOE = 'schmoe';`},
19+
{Code: `let moo = 'car';`},
20+
{Code: `const JOE = 'schmoe'; let moo = 'car';`},
21+
{Code: `for (let i = 0; i < 10; i++) {}`},
22+
{Code: `for (const x of [1,2]) {}`},
23+
},
24+
// Invalid cases - ported from ESLint
25+
[]rule_tester.InvalidTestCase{
26+
{
27+
Code: `var foo = bar;`,
28+
Errors: []rule_tester.InvalidTestCaseError{
29+
{MessageId: "unexpectedVar", Line: 1, Column: 1},
30+
},
31+
},
32+
{
33+
Code: `var foo = bar, toast = most;`,
34+
Errors: []rule_tester.InvalidTestCaseError{
35+
{MessageId: "unexpectedVar", Line: 1, Column: 1},
36+
},
37+
},
38+
{
39+
Code: `var foo = bar; var baz = quux;`,
40+
Errors: []rule_tester.InvalidTestCaseError{
41+
{MessageId: "unexpectedVar", Line: 1, Column: 1},
42+
{MessageId: "unexpectedVar", Line: 1, Column: 16},
43+
},
44+
},
45+
{
46+
Code: `if (true) { var x = 1; }`,
47+
Errors: []rule_tester.InvalidTestCaseError{
48+
{MessageId: "unexpectedVar", Line: 1, Column: 13},
49+
},
50+
},
51+
52+
// for-loop initializer
53+
{
54+
Code: `for (var i = 0; i < 10; i++) {}`,
55+
Errors: []rule_tester.InvalidTestCaseError{
56+
{MessageId: "unexpectedVar", Line: 1, Column: 6},
57+
},
58+
},
59+
60+
// for-in
61+
{
62+
Code: `for (var x in obj) {}`,
63+
Errors: []rule_tester.InvalidTestCaseError{
64+
{MessageId: "unexpectedVar", Line: 1, Column: 6},
65+
},
66+
},
67+
68+
// for-of
69+
{
70+
Code: `for (var x of [1,2]) {}`,
71+
Errors: []rule_tester.InvalidTestCaseError{
72+
{MessageId: "unexpectedVar", Line: 1, Column: 6},
73+
},
74+
},
75+
},
76+
)
77+
}

packages/rslint-test-tools/rstest.config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default defineConfig({
2121
'./tests/eslint/rules/getter-return.test.ts',
2222
'./tests/eslint/rules/no-loss-of-precision.test.ts',
2323

24+
'./tests/eslint/rules/no-var.test.ts',
2425
// eslint-plugin-import
2526
'./tests/eslint-plugin-import/rules/no-self-import.test.ts',
2627
'./tests/eslint-plugin-import/rules/no-webpack-loader-syntax.test.ts',
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Rstest Snapshot v1
2+
3+
exports[`no-var > invalid 1`] = `
4+
{
5+
"code": "var foo = bar;",
6+
"diagnostics": [
7+
{
8+
"message": "Unexpected var, use let or const instead.",
9+
"messageId": "unexpectedVar",
10+
"range": {
11+
"end": {
12+
"column": 15,
13+
"line": 1,
14+
},
15+
"start": {
16+
"column": 1,
17+
"line": 1,
18+
},
19+
},
20+
"ruleName": "no-var",
21+
},
22+
],
23+
"errorCount": 1,
24+
"fileCount": 1,
25+
"ruleCount": 1,
26+
}
27+
`;
28+
29+
exports[`no-var > invalid 2`] = `
30+
{
31+
"code": "var foo = bar, toast = most;",
32+
"diagnostics": [
33+
{
34+
"message": "Unexpected var, use let or const instead.",
35+
"messageId": "unexpectedVar",
36+
"range": {
37+
"end": {
38+
"column": 29,
39+
"line": 1,
40+
},
41+
"start": {
42+
"column": 1,
43+
"line": 1,
44+
},
45+
},
46+
"ruleName": "no-var",
47+
},
48+
],
49+
"errorCount": 1,
50+
"fileCount": 1,
51+
"ruleCount": 1,
52+
}
53+
`;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { RuleTester } from '../rule-tester';
2+
3+
const ruleTester = new RuleTester();
4+
5+
ruleTester.run('no-var', {
6+
valid: [
7+
"const JOE = 'schmoe';",
8+
"let moo = 'car';",
9+
'const a = 1; let b = 2;',
10+
],
11+
invalid: [
12+
{
13+
code: 'var foo = bar;',
14+
errors: [{ messageId: 'unexpectedVar' }],
15+
},
16+
{
17+
code: 'var foo = bar, toast = most;',
18+
errors: [{ messageId: 'unexpectedVar' }],
19+
},
20+
],
21+
});

rslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"@typescript-eslint/prefer-includes": "off",
5858
"@typescript-eslint/prefer-regexp-exec": "off",
5959
"@typescript-eslint/prefer-ts-expect-error": "off",
60-
"no-console": "warn"
60+
"no-console": "warn",
61+
"no-var": "off"
6162
},
6263
"plugins": ["@typescript-eslint"]
6364
}

0 commit comments

Comments
 (0)