Skip to content

Commit b7a9095

Browse files
authored
feat(eslint-plugin-import): implement import/no-self-import rule (#215)
1 parent 343ebcf commit b7a9095

File tree

25 files changed

+759
-122
lines changed

25 files changed

+759
-122
lines changed

cmd/rslint/api.go

Lines changed: 2 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -17,57 +17,6 @@ import (
1717
rslintconfig "github.com/web-infra-dev/rslint/internal/config"
1818
"github.com/web-infra-dev/rslint/internal/linter"
1919
"github.com/web-infra-dev/rslint/internal/rule"
20-
"github.com/web-infra-dev/rslint/internal/rules/adjacent_overload_signatures"
21-
"github.com/web-infra-dev/rslint/internal/rules/array_type"
22-
"github.com/web-infra-dev/rslint/internal/rules/await_thenable"
23-
"github.com/web-infra-dev/rslint/internal/rules/class_literal_property_style"
24-
"github.com/web-infra-dev/rslint/internal/rules/no_array_delete"
25-
"github.com/web-infra-dev/rslint/internal/rules/no_base_to_string"
26-
"github.com/web-infra-dev/rslint/internal/rules/no_confusing_void_expression"
27-
"github.com/web-infra-dev/rslint/internal/rules/no_duplicate_type_constituents"
28-
"github.com/web-infra-dev/rslint/internal/rules/no_empty_function"
29-
"github.com/web-infra-dev/rslint/internal/rules/no_empty_interface"
30-
"github.com/web-infra-dev/rslint/internal/rules/no_floating_promises"
31-
"github.com/web-infra-dev/rslint/internal/rules/no_for_in_array"
32-
"github.com/web-infra-dev/rslint/internal/rules/no_implied_eval"
33-
"github.com/web-infra-dev/rslint/internal/rules/no_meaningless_void_operator"
34-
"github.com/web-infra-dev/rslint/internal/rules/no_misused_promises"
35-
"github.com/web-infra-dev/rslint/internal/rules/no_misused_spread"
36-
"github.com/web-infra-dev/rslint/internal/rules/no_mixed_enums"
37-
"github.com/web-infra-dev/rslint/internal/rules/no_namespace"
38-
"github.com/web-infra-dev/rslint/internal/rules/no_redundant_type_constituents"
39-
"github.com/web-infra-dev/rslint/internal/rules/no_require_imports"
40-
"github.com/web-infra-dev/rslint/internal/rules/no_unnecessary_boolean_literal_compare"
41-
"github.com/web-infra-dev/rslint/internal/rules/no_unnecessary_template_expression"
42-
"github.com/web-infra-dev/rslint/internal/rules/no_unnecessary_type_arguments"
43-
"github.com/web-infra-dev/rslint/internal/rules/no_unnecessary_type_assertion"
44-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_argument"
45-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_assignment"
46-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_call"
47-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_enum_comparison"
48-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_member_access"
49-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_return"
50-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_type_assertion"
51-
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_unary_minus"
52-
"github.com/web-infra-dev/rslint/internal/rules/no_unused_vars"
53-
"github.com/web-infra-dev/rslint/internal/rules/no_useless_empty_export"
54-
"github.com/web-infra-dev/rslint/internal/rules/no_var_requires"
55-
"github.com/web-infra-dev/rslint/internal/rules/non_nullable_type_assertion_style"
56-
"github.com/web-infra-dev/rslint/internal/rules/only_throw_error"
57-
"github.com/web-infra-dev/rslint/internal/rules/prefer_as_const"
58-
"github.com/web-infra-dev/rslint/internal/rules/prefer_promise_reject_errors"
59-
"github.com/web-infra-dev/rslint/internal/rules/prefer_reduce_type_parameter"
60-
"github.com/web-infra-dev/rslint/internal/rules/prefer_return_this_type"
61-
"github.com/web-infra-dev/rslint/internal/rules/promise_function_async"
62-
"github.com/web-infra-dev/rslint/internal/rules/related_getter_setter_pairs"
63-
"github.com/web-infra-dev/rslint/internal/rules/require_array_sort_compare"
64-
"github.com/web-infra-dev/rslint/internal/rules/require_await"
65-
"github.com/web-infra-dev/rslint/internal/rules/restrict_plus_operands"
66-
"github.com/web-infra-dev/rslint/internal/rules/restrict_template_expressions"
67-
"github.com/web-infra-dev/rslint/internal/rules/return_await"
68-
"github.com/web-infra-dev/rslint/internal/rules/switch_exhaustiveness_check"
69-
"github.com/web-infra-dev/rslint/internal/rules/unbound_method"
70-
"github.com/web-infra-dev/rslint/internal/rules/use_unknown_in_catch_callback_variable"
7120
"github.com/web-infra-dev/rslint/internal/utils"
7221
)
7322

@@ -107,73 +56,19 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error)
10756
}
10857

10958
// Initialize rule registry with all available rules
110-
rslintconfig.RegisterAllTypeScriptEslintPluginRules()
59+
rslintconfig.RegisterAllRules()
11160

11261
// Load rslint configuration and determine which tsconfig files to use
11362
_, tsConfigs, configDirectory := rslintconfig.LoadConfigurationWithFallback(req.Config, currentDirectory, fs)
11463

115-
// Create rules
116-
var origin_rules = []rule.Rule{
117-
adjacent_overload_signatures.AdjacentOverloadSignaturesRule,
118-
array_type.ArrayTypeRule,
119-
await_thenable.AwaitThenableRule,
120-
class_literal_property_style.ClassLiteralPropertyStyleRule,
121-
no_array_delete.NoArrayDeleteRule,
122-
no_base_to_string.NoBaseToStringRule,
123-
no_confusing_void_expression.NoConfusingVoidExpressionRule,
124-
no_duplicate_type_constituents.NoDuplicateTypeConstituentsRule,
125-
no_empty_function.NoEmptyFunctionRule,
126-
no_empty_interface.NoEmptyInterfaceRule,
127-
no_floating_promises.NoFloatingPromisesRule,
128-
no_for_in_array.NoForInArrayRule,
129-
no_implied_eval.NoImpliedEvalRule,
130-
no_meaningless_void_operator.NoMeaninglessVoidOperatorRule,
131-
no_misused_promises.NoMisusedPromisesRule,
132-
no_misused_spread.NoMisusedSpreadRule,
133-
no_mixed_enums.NoMixedEnumsRule,
134-
no_namespace.NoNamespaceRule,
135-
no_redundant_type_constituents.NoRedundantTypeConstituentsRule,
136-
no_require_imports.NoRequireImportsRule,
137-
no_unnecessary_boolean_literal_compare.NoUnnecessaryBooleanLiteralCompareRule,
138-
no_unnecessary_template_expression.NoUnnecessaryTemplateExpressionRule,
139-
no_unnecessary_type_arguments.NoUnnecessaryTypeArgumentsRule,
140-
no_unnecessary_type_assertion.NoUnnecessaryTypeAssertionRule,
141-
no_unsafe_argument.NoUnsafeArgumentRule,
142-
no_unsafe_assignment.NoUnsafeAssignmentRule,
143-
no_unsafe_call.NoUnsafeCallRule,
144-
no_unsafe_enum_comparison.NoUnsafeEnumComparisonRule,
145-
no_unsafe_member_access.NoUnsafeMemberAccessRule,
146-
no_unsafe_return.NoUnsafeReturnRule,
147-
no_unsafe_type_assertion.NoUnsafeTypeAssertionRule,
148-
no_unsafe_unary_minus.NoUnsafeUnaryMinusRule,
149-
no_unused_vars.NoUnusedVarsRule,
150-
no_useless_empty_export.NoUselessEmptyExportRule,
151-
no_var_requires.NoVarRequiresRule,
152-
non_nullable_type_assertion_style.NonNullableTypeAssertionStyleRule,
153-
only_throw_error.OnlyThrowErrorRule,
154-
prefer_as_const.PreferAsConstRule,
155-
prefer_promise_reject_errors.PreferPromiseRejectErrorsRule,
156-
prefer_reduce_type_parameter.PreferReduceTypeParameterRule,
157-
prefer_return_this_type.PreferReturnThisTypeRule,
158-
promise_function_async.PromiseFunctionAsyncRule,
159-
related_getter_setter_pairs.RelatedGetterSetterPairsRule,
160-
require_array_sort_compare.RequireArraySortCompareRule,
161-
require_await.RequireAwaitRule,
162-
restrict_plus_operands.RestrictPlusOperandsRule,
163-
restrict_template_expressions.RestrictTemplateExpressionsRule,
164-
return_await.ReturnAwaitRule,
165-
switch_exhaustiveness_check.SwitchExhaustivenessCheckRule,
166-
unbound_method.UnboundMethodRule,
167-
use_unknown_in_catch_callback_variable.UseUnknownInCatchCallbackVariableRule,
168-
}
16964
type RuleWithOption struct {
17065
rule rule.Rule
17166
option interface{}
17267
}
17368
rulesWithOptions := []RuleWithOption{}
17469
// filter rule based on request.RuleOptions
17570
if len(req.RuleOptions) > 0 {
176-
for _, r := range origin_rules {
71+
for _, r := range rslintconfig.GlobalRuleRegistry.GetAllRules() {
17772
if option, ok := req.RuleOptions[r.Name]; ok {
17873
rulesWithOptions = append(rulesWithOptions, RuleWithOption{
17974
rule: r,

cmd/rslint/cmd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ func runCMD() int {
408408
fs := bundled.WrapFS(cachedvfs.From(osvfs.FS()))
409409

410410
// Initialize rule registry with all available rules
411-
rslintconfig.RegisterAllTypeScriptEslintPluginRules()
411+
rslintconfig.RegisterAllRules()
412412
var rslintConfig rslintconfig.RslintConfig
413413
var tsConfigs []string
414414
// Load rslint configuration and determine which rules to enable

cmd/rslint/lsp.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func (s *LSPServer) runDiagnostics(ctx context.Context, uri lsproto.DocumentUri,
247247
}
248248

249249
// Initialize rule registry with all available rules (ensure it's done once)
250-
config.RegisterAllTypeScriptEslintPluginRules()
250+
config.RegisterAllRules()
251251

252252
// Convert URI to file path
253253
filePath := uriToPath(uriString)
@@ -496,7 +496,7 @@ func runLintWithPrograms(uri lsproto.DocumentUri, programs []*compiler.Program,
496496
}
497497

498498
// Initialize rule registry with all available rules
499-
config.RegisterAllTypeScriptEslintPluginRules()
499+
config.RegisterAllRules()
500500

501501
// Collect diagnostics
502502
var diagnostics []rule.RuleDiagnostic

internal/config/config.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/bmatcuk/doublestar/v4"
9+
importPlugin "github.com/web-infra-dev/rslint/internal/plugins/import"
910
"github.com/web-infra-dev/rslint/internal/rule"
1011
"github.com/web-infra-dev/rslint/internal/rules/adjacent_overload_signatures"
1112
"github.com/web-infra-dev/rslint/internal/rules/array_type"
@@ -167,9 +168,14 @@ func (rc *RuleConfig) GetSeverity() rule.DiagnosticSeverity {
167168
return rule.ParseSeverity(rc.Level)
168169
}
169170
func GetAllRulesForPlugin(plugin string) []rule.Rule {
170-
if plugin == "@typescript-eslint" {
171+
switch plugin {
172+
case "@typescript-eslint":
171173
return getAllTypeScriptEslintPluginRules()
172-
} else {
174+
case "eslint-plugin-import":
175+
return importPlugin.GetAllRules()
176+
case "eslint-plugin-import/recommended":
177+
return importPlugin.GetRecommendedRules()
178+
default:
173179
return []rule.Rule{} // Return empty slice for unsupported plugins
174180
}
175181
}
@@ -266,8 +272,13 @@ func (config RslintConfig) GetRulesForFile(filePath string) map[string]*RuleConf
266272
return enabledRules
267273
}
268274

269-
// RegisterAllTypeScriptEslintPluginRules registers all available rules in the global registry
270-
func RegisterAllTypeScriptEslintPluginRules() {
275+
func RegisterAllRules() {
276+
registerAllTypeScriptEslintPluginRules()
277+
registerAllEslintImportPluginRules()
278+
}
279+
280+
// registerAllTypeScriptEslintPluginRules registers all available rules in the global registry
281+
func registerAllTypeScriptEslintPluginRules() {
271282
GlobalRuleRegistry.Register("@typescript-eslint/adjacent-overload-signatures", adjacent_overload_signatures.AdjacentOverloadSignaturesRule)
272283
GlobalRuleRegistry.Register("@typescript-eslint/array-type", array_type.ArrayTypeRule)
273284
GlobalRuleRegistry.Register("@typescript-eslint/await-thenable", await_thenable.AwaitThenableRule)
@@ -321,6 +332,12 @@ func RegisterAllTypeScriptEslintPluginRules() {
321332
GlobalRuleRegistry.Register("@typescript-eslint/use-unknown-in-catch-callback-variable", use_unknown_in_catch_callback_variable.UseUnknownInCatchCallbackVariableRule)
322333
}
323334

335+
func registerAllEslintImportPluginRules() {
336+
for _, rule := range importPlugin.GetAllRules() {
337+
GlobalRuleRegistry.Register(rule.Name, rule)
338+
}
339+
}
340+
324341
// getAllTypeScriptEslintPluginRules returns all registered rules (for backward compatibility when no config is provided)
325342
func getAllTypeScriptEslintPluginRules() []rule.Rule {
326343
allRules := GlobalRuleRegistry.GetAllRules()

internal/config/config_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,46 @@ func TestGetRulesForFileWithIgnores(t *testing.T) {
204204
}
205205
}
206206

207+
func TestGetImportPluginRules(t *testing.T) {
208+
baseConfig := ConfigEntry{
209+
Language: "typescript",
210+
Files: []string{"**/*.ts", "**/*.tsx"},
211+
Ignores: []string{"**/*.test.ts", "node_modules/**"},
212+
Rules: Rules{},
213+
}
214+
215+
tests := []struct {
216+
rulesCount int
217+
plugin string
218+
}{
219+
{
220+
rulesCount: 1,
221+
plugin: "eslint-plugin-import",
222+
},
223+
{
224+
rulesCount: 0,
225+
plugin: "eslint-plugin-import/recommended",
226+
},
227+
}
228+
229+
for _, tt := range tests {
230+
t.Run(tt.plugin, func(t *testing.T) {
231+
config := RslintConfig{
232+
baseConfig,
233+
{
234+
Plugins: []string{tt.plugin},
235+
},
236+
}
237+
rules := config.GetRulesForFile("foo.ts")
238+
239+
if len(rules) != tt.rulesCount {
240+
t.Errorf("GetRulesForFile(foo.ts) with plugin %v ruleCount = %v, expected %v (rules: %v)",
241+
tt.plugin, len(rules), tt.rulesCount, rules)
242+
}
243+
})
244+
}
245+
}
246+
207247
func TestParseArrayRuleConfig(t *testing.T) {
208248
tests := []struct {
209249
name string

internal/plugins/import/all.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package import_plugin
2+
3+
import (
4+
"github.com/web-infra-dev/rslint/internal/plugins/import/rules/no_self_import"
5+
"github.com/web-infra-dev/rslint/internal/rule"
6+
)
7+
8+
func GetAllRules() []rule.Rule {
9+
return []rule.Rule{
10+
no_self_import.NoSelfImportRule,
11+
}
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package import_plugin
2+
3+
import (
4+
"github.com/web-infra-dev/rslint/internal/rule"
5+
)
6+
7+
func GetRecommendedRules() []rule.Rule {
8+
return []rule.Rule{}
9+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package no_self_import
2+
3+
import (
4+
"github.com/microsoft/typescript-go/shim/ast"
5+
"github.com/web-infra-dev/rslint/internal/plugins/import/utils"
6+
"github.com/web-infra-dev/rslint/internal/rule"
7+
)
8+
9+
// See: https://github.com/import-js/eslint-plugin-import/blob/01c9eb04331d2efa8d63f2d7f4bfec3bc44c94f3/src/rules/no-self-import.js
10+
var NoSelfImportRule = rule.Rule{
11+
Name: "import/no-self-import",
12+
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
13+
return utils.VisitModules(func(source, node *ast.Node) {
14+
isImportingSelf(ctx, source, node)
15+
}, utils.VisitModulesOptions{
16+
Commonjs: true,
17+
ESModule: true,
18+
})
19+
},
20+
}
21+
22+
// https://github.com/import-js/eslint-plugin-import/blob/01c9eb04331d2efa8d63f2d7f4bfec3bc44c94f3/src/rules/no-self-import.js#L12-L22
23+
func isImportingSelf(ctx rule.RuleContext, source *ast.StringLiteralLike, node *ast.ImportSpecifierNode) {
24+
filePath := utils.GetPhysicalFilename(ctx)
25+
26+
if resolvedPath, ok := utils.Resolve(source, ctx); ok {
27+
if /** filePath != "<text>" && */ filePath == resolvedPath {
28+
ctx.ReportNode(node, rule.RuleMessage{
29+
Id: "import/no-self-import",
30+
Description: "Module imports itself.",
31+
})
32+
}
33+
}
34+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package no_self_import_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/web-infra-dev/rslint/internal/plugins/import/rules/no_self_import"
7+
"github.com/web-infra-dev/rslint/internal/rule_tester"
8+
"github.com/web-infra-dev/rslint/internal/rules/fixtures"
9+
)
10+
11+
func TestNoSelfImportRule(t *testing.T) {
12+
errors := make([]rule_tester.InvalidTestCaseError, 6)
13+
for i, err := range errors {
14+
err.MessageId = "import/no-self-import"
15+
err.Line = i + 2
16+
err.Column = 1
17+
errors[i] = err
18+
}
19+
20+
rule_tester.RunRuleTester(
21+
fixtures.GetRootDir(),
22+
"tsconfig.json",
23+
t,
24+
&no_self_import.NoSelfImportRule,
25+
[]rule_tester.ValidTestCase{
26+
{Code: `import { bar } from "./bar.ts"`, FileName: "foo.ts"},
27+
{Code: `import { bar } from "./bar.json" with { type: "json" }`, FileName: "foo.ts"},
28+
{Code: `require("./bar.ts")`, FileName: "foo.ts"},
29+
{Code: `require()`, FileName: "foo.ts"},
30+
{Code: `require(123)`, FileName: "foo.ts"},
31+
{Code: `require("./foo.ts", 123)`, FileName: "foo.ts"},
32+
},
33+
[]rule_tester.InvalidTestCase{
34+
{
35+
Code: `
36+
import './foo.ts';
37+
import * as fooStar from './foo.ts';
38+
import fooDefault from './foo.ts';
39+
import { foo } from './foo.ts';
40+
import('./foo.ts');
41+
import('./foo.ts', { with: { type: 'module' } });
42+
`,
43+
FileName: "foo.ts",
44+
Errors: errors,
45+
},
46+
{
47+
Code: `
48+
import './foo.js';
49+
import * as fooStar from './foo.js';
50+
import fooDefault from './foo.js';
51+
import { foo } from './foo.js';
52+
import('./foo.js');
53+
import('./foo.js', { with: { type: 'module' } });
54+
`,
55+
FileName: "foo.ts",
56+
Errors: errors,
57+
},
58+
{
59+
Code: `
60+
import './foo';
61+
import * as fooStar from './foo';
62+
import fooDefault from './foo';
63+
import { foo } from './foo';
64+
import('./foo');
65+
import('./foo', { with: { type: 'module' } });
66+
`,
67+
FileName: "foo.ts",
68+
Errors: errors,
69+
},
70+
},
71+
)
72+
}

0 commit comments

Comments
 (0)