Skip to content

Commit 473b340

Browse files
authored
feat: implement no-namespace (#197)
Co-authored-by: heyongqi10 <[email protected]>
1 parent 200fabf commit 473b340

File tree

8 files changed

+498
-2
lines changed

8 files changed

+498
-2
lines changed

cmd/rslint/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/web-infra-dev/rslint/internal/rules/no_misused_promises"
3434
"github.com/web-infra-dev/rslint/internal/rules/no_misused_spread"
3535
"github.com/web-infra-dev/rslint/internal/rules/no_mixed_enums"
36+
"github.com/web-infra-dev/rslint/internal/rules/no_namespace"
3637
"github.com/web-infra-dev/rslint/internal/rules/no_redundant_type_constituents"
3738
"github.com/web-infra-dev/rslint/internal/rules/no_require_imports"
3839
"github.com/web-infra-dev/rslint/internal/rules/no_unnecessary_boolean_literal_compare"
@@ -124,6 +125,7 @@ func (h *IPCHandler) HandleLint(req api.LintRequest) (*api.LintResponse, error)
124125
no_misused_promises.NoMisusedPromisesRule,
125126
no_misused_spread.NoMisusedSpreadRule,
126127
no_mixed_enums.NoMixedEnumsRule,
128+
no_namespace.NoNamespaceRule,
127129
no_redundant_type_constituents.NoRedundantTypeConstituentsRule,
128130
no_require_imports.NoRequireImportsRule,
129131
no_unnecessary_boolean_literal_compare.NoUnnecessaryBooleanLiteralCompareRule,

internal/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/web-infra-dev/rslint/internal/rules/no_misused_promises"
2525
"github.com/web-infra-dev/rslint/internal/rules/no_misused_spread"
2626
"github.com/web-infra-dev/rslint/internal/rules/no_mixed_enums"
27+
"github.com/web-infra-dev/rslint/internal/rules/no_namespace"
2728
"github.com/web-infra-dev/rslint/internal/rules/no_redundant_type_constituents"
2829
"github.com/web-infra-dev/rslint/internal/rules/no_require_imports"
2930
"github.com/web-infra-dev/rslint/internal/rules/no_unnecessary_boolean_literal_compare"
@@ -284,6 +285,7 @@ func RegisterAllTypeScriptEslintPluginRules() {
284285
GlobalRuleRegistry.Register("@typescript-eslint/no-misused-promises", no_misused_promises.NoMisusedPromisesRule)
285286
GlobalRuleRegistry.Register("@typescript-eslint/no-misused-spread", no_misused_spread.NoMisusedSpreadRule)
286287
GlobalRuleRegistry.Register("@typescript-eslint/no-mixed-enums", no_mixed_enums.NoMixedEnumsRule)
288+
GlobalRuleRegistry.Register("@typescript-eslint/no-namespace", no_namespace.NoNamespaceRule)
287289
GlobalRuleRegistry.Register("@typescript-eslint/no-redundant-type-constituents", no_redundant_type_constituents.NoRedundantTypeConstituentsRule)
288290
GlobalRuleRegistry.Register("@typescript-eslint/no-require-imports", no_require_imports.NoRequireImportsRule)
289291
GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-boolean-literal-compare", no_unnecessary_boolean_literal_compare.NoUnnecessaryBooleanLiteralCompareRule)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package no_namespace
2+
3+
import (
4+
"strings"
5+
6+
"github.com/microsoft/typescript-go/shim/ast"
7+
"github.com/web-infra-dev/rslint/internal/rule"
8+
"github.com/web-infra-dev/rslint/internal/utils"
9+
)
10+
11+
// build the message for no-namespace rule
12+
func buildNoNamespaceMessage() rule.RuleMessage {
13+
return rule.RuleMessage{
14+
Id: "moduleSyntaxIsPreferred",
15+
Description: "Namespace is not allowed.",
16+
}
17+
}
18+
19+
// rule options
20+
type NoNamespaceOptions struct {
21+
AllowDeclarations *bool `json:"allowDeclarations"`
22+
AllowDefinitionFiles *bool `json:"allowDefinitionFiles"`
23+
}
24+
25+
// default options
26+
var defaultNoNamespaceOptions = NoNamespaceOptions{
27+
AllowDeclarations: utils.Ref(false),
28+
AllowDefinitionFiles: utils.Ref(true),
29+
}
30+
31+
// rule instance
32+
// check if the namespace is used
33+
var NoNamespaceRule = rule.CreateRule(rule.Rule{
34+
Name: "no-namespace",
35+
Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
36+
opts := defaultNoNamespaceOptions
37+
38+
// Parse options with dual-format support (handles both array and object formats)
39+
if options != nil {
40+
var optsMap map[string]interface{}
41+
var ok bool
42+
43+
// Handle array format: [{ option: value }]
44+
if optArray, isArray := options.([]interface{}); isArray && len(optArray) > 0 {
45+
optsMap, ok = optArray[0].(map[string]interface{})
46+
} else {
47+
// Handle direct object format: { option: value }
48+
optsMap, ok = options.(map[string]interface{})
49+
}
50+
51+
if ok {
52+
if allowDeclarations, ok := optsMap["allowDeclarations"].(bool); ok {
53+
opts.AllowDeclarations = utils.Ref(allowDeclarations)
54+
}
55+
if allowDefinitionFiles, ok := optsMap["allowDefinitionFiles"].(bool); ok {
56+
opts.AllowDefinitionFiles = utils.Ref(allowDefinitionFiles)
57+
}
58+
}
59+
}
60+
61+
return rule.RuleListeners{
62+
ast.KindModuleDeclaration: func(node *ast.Node) {
63+
moduleDecl := node.AsModuleDeclaration()
64+
if moduleDecl == nil {
65+
return
66+
}
67+
68+
// Check if this is a namespace declaration (keyword is KindNamespaceKeyword)
69+
if moduleDecl.Keyword != ast.KindNamespaceKeyword {
70+
return
71+
}
72+
73+
// Check if we're in a .d.ts file and allowDefinitionFiles is true
74+
if opts.AllowDefinitionFiles != nil && *opts.AllowDefinitionFiles && strings.HasSuffix(ctx.SourceFile.FileName(), ".d.ts") {
75+
return
76+
}
77+
78+
// Check if this is a declare namespace and allowDeclarations is true
79+
if opts.AllowDeclarations != nil && *opts.AllowDeclarations && utils.IncludesModifier(node, ast.KindDeclareKeyword) {
80+
return
81+
}
82+
83+
// Report the namespace usage
84+
ctx.ReportNode(moduleDecl.Name(), buildNoNamespaceMessage())
85+
},
86+
}
87+
},
88+
})

0 commit comments

Comments
 (0)