@ttsc/lint 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +95 -43
  2. package/go.mod +5 -4
  3. package/internal/rulecode/rulecode.go +88 -0
  4. package/lib/index.d.ts +10 -1
  5. package/lib/index.js +45 -17
  6. package/lib/index.js.map +1 -1
  7. package/lib/structures/ITtscLintConfig.d.ts +3 -0
  8. package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
  9. package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
  10. package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
  11. package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
  12. package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
  13. package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
  14. package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
  15. package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
  16. package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
  17. package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
  18. package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
  19. package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
  20. package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
  21. package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
  22. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
  23. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
  24. package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
  25. package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
  26. package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
  27. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
  28. package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
  29. package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
  30. package/lib/structures/rules/index.d.ts +3 -0
  31. package/lib/structures/rules/index.js +3 -0
  32. package/lib/structures/rules/index.js.map +1 -1
  33. package/linthost/ast_helpers.go +521 -23
  34. package/linthost/ast_selector_matcher.go +1628 -0
  35. package/linthost/ast_selector_parser.go +628 -0
  36. package/linthost/comment_scan.go +154 -0
  37. package/linthost/compile.go +129 -79
  38. package/linthost/config.go +352 -88
  39. package/linthost/config_format.go +15 -1
  40. package/linthost/contrib_adapter.go +47 -8
  41. package/linthost/declaration_rules.go +12 -11
  42. package/linthost/directives.go +9 -67
  43. package/linthost/dispatch.go +37 -3
  44. package/linthost/engine.go +368 -53
  45. package/linthost/escape_string.go +77 -0
  46. package/linthost/fix.go +133 -23
  47. package/linthost/flags_gen.go +18 -17
  48. package/linthost/format.go +73 -167
  49. package/linthost/format_editor_settings.go +134 -26
  50. package/linthost/grapheme_tables_gen.go +1920 -0
  51. package/linthost/host.go +160 -61
  52. package/linthost/literal_escape_scan.go +117 -0
  53. package/linthost/lsp.go +545 -98
  54. package/linthost/numeric_literal_precision.go +228 -0
  55. package/linthost/polyfill_browserslist.go +1439 -0
  56. package/linthost/polyfill_browserslist_config.go +603 -0
  57. package/linthost/polyfill_corejs.go +750 -0
  58. package/linthost/polyfill_data.go +113 -0
  59. package/linthost/polyfill_data_gen.json +39868 -0
  60. package/linthost/print_nodes_array.go +7 -1
  61. package/linthost/print_nodes_object.go +7 -1
  62. package/linthost/project_engine.go +151 -0
  63. package/linthost/project_rules.go +78 -0
  64. package/linthost/regex_clean.go +191 -0
  65. package/linthost/regex_tree.go +1544 -0
  66. package/linthost/regex_tree_optimizer.go +1685 -0
  67. package/linthost/regexp_cache.go +44 -0
  68. package/linthost/rule_codes.go +105 -0
  69. package/linthost/rule_codes.json +745 -0
  70. package/linthost/rules_ban_ts_comment.go +312 -0
  71. package/linthost/rules_boundaries.go +128 -43
  72. package/linthost/rules_boundaries_dependencies.go +1013 -0
  73. package/linthost/rules_core_extra.go +62 -92
  74. package/linthost/rules_cypress.go +1 -1
  75. package/linthost/rules_default_case.go +87 -11
  76. package/linthost/rules_dupes.go +38 -13
  77. package/linthost/rules_empty.go +188 -20
  78. package/linthost/rules_escape.go +62 -48
  79. package/linthost/rules_format_arrow_parens.go +19 -5
  80. package/linthost/rules_format_bracket_spacing.go +1 -1
  81. package/linthost/rules_format_clause_join.go +1 -1
  82. package/linthost/rules_format_declaration_header.go +31 -19
  83. package/linthost/rules_format_indent.go +1 -1
  84. package/linthost/rules_format_jsdoc.go +1 -1
  85. package/linthost/rules_format_orphan_semi.go +1 -1
  86. package/linthost/rules_format_parameter_properties.go +17 -7
  87. package/linthost/rules_format_print_width.go +1 -1
  88. package/linthost/rules_format_quote_props.go +1 -1
  89. package/linthost/rules_format_quotes.go +1 -1
  90. package/linthost/rules_format_semi.go +106 -79
  91. package/linthost/rules_format_sort_imports.go +79 -45
  92. package/linthost/rules_format_statement_split.go +1 -1
  93. package/linthost/rules_format_ternary_nullish_parens.go +1 -1
  94. package/linthost/rules_format_trailing_comma.go +64 -1
  95. package/linthost/rules_format_whitespace.go +1 -1
  96. package/linthost/rules_functional.go +21 -22
  97. package/linthost/rules_gap.go +80 -3
  98. package/linthost/rules_grouped_accessor_pairs.go +84 -25
  99. package/linthost/rules_guard_for_in.go +64 -147
  100. package/linthost/rules_jsx_a11y.go +232 -22
  101. package/linthost/rules_logic.go +84 -38
  102. package/linthost/rules_nextjs.go +105 -16
  103. package/linthost/rules_no_class_assign.go +70 -0
  104. package/linthost/rules_no_duplicate_imports.go +266 -0
  105. package/linthost/rules_no_else_return.go +156 -38
  106. package/linthost/rules_no_extend_native.go +113 -17
  107. package/linthost/rules_no_fallthrough.go +1013 -0
  108. package/linthost/rules_no_func_assign.go +70 -0
  109. package/linthost/rules_no_import_assign.go +278 -86
  110. package/linthost/rules_no_inner_declarations.go +186 -0
  111. package/linthost/rules_no_loop_func.go +438 -53
  112. package/linthost/rules_no_mixed_operators.go +208 -52
  113. package/linthost/rules_no_param_reassign.go +376 -61
  114. package/linthost/rules_no_promise_executor_return.go +156 -0
  115. package/linthost/rules_no_restricted_imports.go +1013 -57
  116. package/linthost/rules_no_restricted_syntax.go +190 -26
  117. package/linthost/rules_no_useless_constructor.go +6 -36
  118. package/linthost/rules_problems.go +154 -311
  119. package/linthost/rules_promise.go +2578 -109
  120. package/linthost/rules_react_extras.go +71 -45
  121. package/linthost/rules_react_hooks.go +18 -2
  122. package/linthost/rules_react_perf.go +1 -0
  123. package/linthost/rules_react_refresh.go +1 -1
  124. package/linthost/rules_regexp.go +230 -14
  125. package/linthost/rules_security.go +45 -19
  126. package/linthost/rules_self.go +21 -1
  127. package/linthost/rules_solid.go +13 -7
  128. package/linthost/rules_storybook.go +1 -1
  129. package/linthost/rules_suggestions.go +490 -84
  130. package/linthost/rules_testing_library.go +12 -1
  131. package/linthost/rules_ts.go +227 -54
  132. package/linthost/rules_ts_async.go +0 -131
  133. package/linthost/rules_ts_no_base_to_string.go +3 -4
  134. package/linthost/rules_ts_no_misused_promises.go +817 -0
  135. package/linthost/rules_ts_no_restricted_types.go +320 -34
  136. package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
  137. package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
  138. package/linthost/rules_ts_require_array_sort_compare.go +5 -5
  139. package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
  140. package/linthost/rules_unicorn_better_regex.go +224 -16
  141. package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
  142. package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
  143. package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
  144. package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
  145. package/linthost/rules_unicorn_escape_case.go +30 -19
  146. package/linthost/rules_unicorn_filename_case.go +763 -16
  147. package/linthost/rules_unicorn_import_style.go +783 -17
  148. package/linthost/rules_unicorn_isolated_functions.go +934 -14
  149. package/linthost/rules_unicorn_no_hex_escape.go +26 -11
  150. package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
  151. package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
  152. package/linthost/rules_unicorn_no_unused_properties.go +756 -19
  153. package/linthost/rules_unicorn_number_literal_case.go +58 -39
  154. package/linthost/rules_unicorn_prefer_at.go +7 -3
  155. package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
  156. package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
  157. package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
  158. package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
  159. package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
  160. package/linthost/rules_unicorn_string_content.go +611 -15
  161. package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
  162. package/linthost/rules_unicorn_template_indent.go +923 -18
  163. package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
  164. package/linthost/rules_unicorn_throw_new_error.go +218 -26
  165. package/linthost/rules_var.go +829 -142
  166. package/linthost/string_length.go +222 -0
  167. package/package.json +4 -3
  168. package/rule/project.go +145 -0
  169. package/rule/rule.go +58 -1
  170. package/src/index.ts +58 -17
  171. package/src/structures/ITtscLintConfig.ts +3 -0
  172. package/src/structures/TtscLintRuleSetting.ts +3 -2
  173. package/src/structures/format/ITtscLintFormat.ts +3 -2
  174. package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
  175. package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
  176. package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
  177. package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
  178. package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
  179. package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
  180. package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
  181. package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
  182. package/src/structures/rules/ITtscLintRules.ts +7 -2
  183. package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
  184. package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
  185. package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
  186. package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
  187. package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
  188. package/src/structures/rules/index.ts +3 -0
@@ -0,0 +1,70 @@
1
+ // noFuncAssign rejects writes to bindings introduced by function declarations
2
+ // and named function expressions. It follows checker symbols instead of names,
3
+ // so hoisted references resolve correctly while parameter, catch, block, and
4
+ // sibling-scope shadows remain independent.
5
+ // https://eslint.org/docs/latest/rules/no-func-assign
6
+ package linthost
7
+
8
+ import shimast "github.com/microsoft/typescript-go/shim/ast"
9
+
10
+ type noFuncAssign struct{}
11
+
12
+ func (noFuncAssign) Name() string { return "no-func-assign" }
13
+ func (noFuncAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
14
+ func (noFuncAssign) NeedsTypeChecker() bool { return true }
15
+ func (noFuncAssign) Check(ctx *Context, node *shimast.Node) {
16
+ if ctx == nil || ctx.Checker == nil || node == nil {
17
+ return
18
+ }
19
+
20
+ functionSymbols := make(map[*shimast.Symbol]struct{})
21
+ walkDescendants(node, func(child *shimast.Node) {
22
+ name := noFuncAssignDeclarationName(child)
23
+ symbol := canonicalValueSymbol(ctx, name)
24
+ if symbol != nil {
25
+ functionSymbols[symbol] = struct{}{}
26
+ }
27
+ })
28
+ if len(functionSymbols) == 0 {
29
+ return
30
+ }
31
+
32
+ reported := make(map[*shimast.Node]struct{})
33
+ walkDescendants(node, func(child *shimast.Node) {
34
+ for _, target := range writeTargetIdentifiers(child) {
35
+ if _, duplicate := reported[target]; duplicate {
36
+ continue
37
+ }
38
+ symbol := canonicalValueSymbol(ctx, target)
39
+ if _, isFunction := functionSymbols[symbol]; !isFunction {
40
+ continue
41
+ }
42
+ reported[target] = struct{}{}
43
+ ctx.Report(target, "'"+identifierText(target)+"' is a function.")
44
+ }
45
+ })
46
+ }
47
+
48
+ // noFuncAssignDeclarationName returns the value-binding name introduced by a
49
+ // function declaration or named function expression. Anonymous expressions do
50
+ // not introduce a function-name binding.
51
+ func noFuncAssignDeclarationName(node *shimast.Node) *shimast.Node {
52
+ if node == nil {
53
+ return nil
54
+ }
55
+ switch node.Kind {
56
+ case shimast.KindFunctionDeclaration:
57
+ if declaration := node.AsFunctionDeclaration(); declaration != nil {
58
+ return declaration.Name()
59
+ }
60
+ case shimast.KindFunctionExpression:
61
+ if expression := node.AsFunctionExpression(); expression != nil {
62
+ return expression.Name()
63
+ }
64
+ }
65
+ return nil
66
+ }
67
+
68
+ func init() {
69
+ Register(noFuncAssign{})
70
+ }
@@ -1,138 +1,330 @@
1
- // noImportAssign: writes to a binding introduced by an `import`
2
- // declaration. Imported bindings are read-only at runtime; mutating
3
- // them either throws under strict mode or silently desynchronises the
4
- // module's view of its own exports.
1
+ // noImportAssign reports writes to bindings introduced by imports. Binding
2
+ // identity comes from TypeScript's checker, so a same-spelled declaration in a
3
+ // nested scope is independent from the import it shadows.
5
4
  //
6
- // Conservative scope tracking: collect the local names introduced by
7
- // every `import` declaration in the file (default specifier, named
8
- // specifier, namespace alias), then walk descendants once and flag
9
- // `name = …` / `name op= …` / `++name` / `name--` assignments whose
10
- // left-hand side is one of those names. Namespace imports are also
11
- // flagged on `ns.foo = …` / `++ns.foo` because the namespace object
12
- // is itself frozen — mutating any property throws in strict mode.
13
- //
14
- // Like `no-param-reassign`, this is an AST-only baseline: a later
15
- // `const name = …` inside an inner scope that shadows an imported
16
- // name will produce a false positive. Proper resolution needs the
17
- // real binder, which the AST-only baseline avoids.
5
+ // Namespace imports additionally protect their direct members. This follows
6
+ // ESLint's contract for assignments, updates, deletes, loop targets, and the
7
+ // standard Object/Reflect mutation functions without treating deeper exported
8
+ // objects as namespace members.
18
9
  // https://eslint.org/docs/latest/rules/no-import-assign
19
10
  package linthost
20
11
 
21
- import shimast "github.com/microsoft/typescript-go/shim/ast"
12
+ import (
13
+ shimast "github.com/microsoft/typescript-go/shim/ast"
14
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
15
+ )
22
16
 
23
17
  type noImportAssign struct{}
24
18
 
25
19
  func (noImportAssign) Name() string { return "no-import-assign" }
26
20
  func (noImportAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
21
+ func (noImportAssign) NeedsTypeChecker() bool { return true }
22
+
27
23
  func (noImportAssign) Check(ctx *Context, node *shimast.Node) {
28
- if node == nil {
24
+ if ctx == nil || ctx.Checker == nil || node == nil {
29
25
  return
30
26
  }
31
- imported := map[string]bool{}
32
- namespaces := map[string]bool{}
33
- collectImportedBindings(node, imported, namespaces)
27
+ imported, namespaces := collectNoImportAssignBindings(ctx, node)
34
28
  if len(imported) == 0 {
35
29
  return
36
30
  }
37
- walkDescendants(node, func(n *shimast.Node) {
38
- switch n.Kind {
31
+
32
+ walkDescendants(node, func(candidate *shimast.Node) {
33
+ switch candidate.Kind {
39
34
  case shimast.KindBinaryExpression:
40
- expr := n.AsBinaryExpression()
41
- if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind) {
35
+ expression := candidate.AsBinaryExpression()
36
+ if expression == nil || expression.OperatorToken == nil ||
37
+ !isAssignmentOperator(expression.OperatorToken.Kind) ||
38
+ isDestructuringAssignmentTarget(candidate) {
42
39
  return
43
40
  }
44
- reportImportTarget(ctx, expr.Left, imported, namespaces)
41
+ reportNoImportAssignTarget(ctx, candidate, expression.Left, imported, namespaces, true)
42
+
45
43
  case shimast.KindPrefixUnaryExpression:
46
- pre := n.AsPrefixUnaryExpression()
47
- if pre == nil || (pre.Operator != shimast.KindPlusPlusToken && pre.Operator != shimast.KindMinusMinusToken) {
44
+ expression := candidate.AsPrefixUnaryExpression()
45
+ if expression == nil ||
46
+ (expression.Operator != shimast.KindPlusPlusToken && expression.Operator != shimast.KindMinusMinusToken) {
48
47
  return
49
48
  }
50
- reportImportTarget(ctx, pre.Operand, imported, namespaces)
49
+ reportNoImportAssignTarget(ctx, candidate, expression.Operand, imported, namespaces, true)
50
+
51
51
  case shimast.KindPostfixUnaryExpression:
52
- post := n.AsPostfixUnaryExpression()
53
- if post == nil || (post.Operator != shimast.KindPlusPlusToken && post.Operator != shimast.KindMinusMinusToken) {
52
+ expression := candidate.AsPostfixUnaryExpression()
53
+ if expression == nil ||
54
+ (expression.Operator != shimast.KindPlusPlusToken && expression.Operator != shimast.KindMinusMinusToken) {
55
+ return
56
+ }
57
+ reportNoImportAssignTarget(ctx, candidate, expression.Operand, imported, namespaces, true)
58
+
59
+ case shimast.KindDeleteExpression:
60
+ expression := candidate.AsDeleteExpression()
61
+ if expression != nil {
62
+ // ESLint treats delete as a namespace-member mutation. Bare delete is
63
+ // covered by no-delete-var rather than as an import assignment.
64
+ reportNoImportAssignTarget(ctx, candidate, expression.Expression, imported, namespaces, false)
65
+ }
66
+
67
+ case shimast.KindForInStatement, shimast.KindForOfStatement:
68
+ statement := candidate.AsForInOrOfStatement()
69
+ if statement == nil || statement.Initializer == nil ||
70
+ statement.Initializer.Kind == shimast.KindVariableDeclarationList {
54
71
  return
55
72
  }
56
- reportImportTarget(ctx, post.Operand, imported, namespaces)
73
+ reportNoImportAssignTarget(ctx, candidate, statement.Initializer, imported, namespaces, true)
74
+
75
+ case shimast.KindCallExpression:
76
+ reportNoImportAssignMutationCall(ctx, candidate, namespaces)
57
77
  }
58
78
  })
59
79
  }
60
80
 
61
- // collectImportedBindings walks every `import` declaration in `file` and
62
- // records the local names introduced. Namespace bindings (`* as ns`) are
63
- // additionally remembered so callers can flag property mutations like
64
- // `ns.foo = …`.
65
- func collectImportedBindings(file *shimast.Node, imported, namespaces map[string]bool) {
66
- walkDescendants(file, func(n *shimast.Node) {
67
- if n == nil || n.Kind != shimast.KindImportDeclaration {
68
- return
69
- }
70
- decl := n.AsImportDeclaration()
71
- if decl == nil || decl.ImportClause == nil {
72
- return
73
- }
74
- clause := decl.ImportClause.AsImportClause()
75
- if clause == nil {
81
+ // collectNoImportAssignBindings records the checker symbol owned by every
82
+ // default, named, namespace, type-only, and import-equals local name. References
83
+ // are compared against these symbols rather than their text.
84
+ func collectNoImportAssignBindings(
85
+ ctx *Context,
86
+ file *shimast.Node,
87
+ ) (map[*shimast.Symbol]string, map[*shimast.Symbol]string) {
88
+ imported := map[*shimast.Symbol]string{}
89
+ namespaces := map[*shimast.Symbol]string{}
90
+ add := func(name *shimast.Node, namespace bool) {
91
+ symbol := noImportAssignValueSymbol(ctx, name)
92
+ localName := identifierText(name)
93
+ if symbol == nil || localName == "" {
76
94
  return
77
95
  }
78
- // `import Default from "x"` — default specifier.
79
- if name := identifierText(clause.Name()); name != "" {
80
- imported[name] = true
96
+ imported[symbol] = localName
97
+ if namespace {
98
+ namespaces[symbol] = localName
81
99
  }
82
- if clause.NamedBindings == nil {
83
- return
84
- }
85
- switch clause.NamedBindings.Kind {
86
- case shimast.KindNamedImports:
87
- named := clause.NamedBindings.AsNamedImports()
88
- if named == nil || named.Elements == nil {
100
+ }
101
+
102
+ walkDescendants(file, func(node *shimast.Node) {
103
+ switch node.Kind {
104
+ case shimast.KindImportDeclaration:
105
+ declaration := node.AsImportDeclaration()
106
+ if declaration == nil || declaration.ImportClause == nil {
89
107
  return
90
108
  }
91
- for _, specNode := range named.Elements.Nodes {
92
- spec := specNode.AsImportSpecifier()
93
- if spec == nil {
94
- continue
95
- }
96
- if name := identifierText(spec.Name()); name != "" {
97
- imported[name] = true
98
- }
109
+ clause := declaration.ImportClause.AsImportClause()
110
+ if clause == nil {
111
+ return
99
112
  }
100
- case shimast.KindNamespaceImport:
101
- ns := clause.NamedBindings.AsNamespaceImport()
102
- if ns == nil {
113
+ add(clause.Name(), false)
114
+ if clause.NamedBindings == nil {
103
115
  return
104
116
  }
105
- if name := identifierText(ns.Name()); name != "" {
106
- imported[name] = true
107
- namespaces[name] = true
117
+ switch clause.NamedBindings.Kind {
118
+ case shimast.KindNamedImports:
119
+ named := clause.NamedBindings.AsNamedImports()
120
+ if named == nil || named.Elements == nil {
121
+ return
122
+ }
123
+ for _, element := range named.Elements.Nodes {
124
+ if specifier := element.AsImportSpecifier(); specifier != nil {
125
+ add(specifier.Name(), false)
126
+ }
127
+ }
128
+ case shimast.KindNamespaceImport:
129
+ if namespace := clause.NamedBindings.AsNamespaceImport(); namespace != nil {
130
+ add(namespace.Name(), true)
131
+ }
132
+ }
133
+
134
+ case shimast.KindImportEqualsDeclaration:
135
+ if declaration := node.AsImportEqualsDeclaration(); declaration != nil {
136
+ add(declaration.Name(), false)
108
137
  }
109
138
  }
110
139
  })
140
+ return imported, namespaces
111
141
  }
112
142
 
113
- // reportImportTarget flags `target` when it writes to an imported name.
114
- // A bare identifier matching an imported binding is always flagged; a
115
- // `ns.<key>` property access is flagged when `ns` is a namespace import.
116
- func reportImportTarget(ctx *Context, target *shimast.Node, imported, namespaces map[string]bool) {
117
- stripped := stripParens(target)
118
- if stripped == nil {
143
+ // reportNoImportAssignTarget walks only the write positions of one assignment
144
+ // target. Object keys, computed keys, and default initializers are reads; member
145
+ // accesses are leaves so `ns.export.value = ...` remains allowed while a direct
146
+ // `ns.export = ...` write is reported.
147
+ func reportNoImportAssignTarget(
148
+ ctx *Context,
149
+ reportNode *shimast.Node,
150
+ target *shimast.Node,
151
+ imported map[*shimast.Symbol]string,
152
+ namespaces map[*shimast.Symbol]string,
153
+ allowBinding bool,
154
+ ) {
155
+ target = unwrapReferenceExpression(target)
156
+ if target == nil {
119
157
  return
120
158
  }
121
- if name := identifierText(stripped); name != "" {
122
- if imported[name] {
123
- ctx.Report(target, "'"+name+"' is read-only.")
159
+ switch target.Kind {
160
+ case shimast.KindIdentifier:
161
+ if !allowBinding {
162
+ return
163
+ }
164
+ if symbol := noImportAssignValueSymbol(ctx, target); symbol != nil {
165
+ if name, ok := imported[symbol]; ok {
166
+ ctx.Report(reportNode, "'"+name+"' is read-only.")
167
+ }
168
+ }
169
+
170
+ case shimast.KindPropertyAccessExpression, shimast.KindElementAccessExpression:
171
+ if name := noImportAssignDirectNamespaceName(ctx, target, namespaces); name != "" {
172
+ ctx.Report(reportNode, "The members of '"+name+"' are read-only.")
173
+ }
174
+
175
+ case shimast.KindArrayLiteralExpression:
176
+ if array := target.AsArrayLiteralExpression(); array != nil && array.Elements != nil {
177
+ for _, element := range array.Elements.Nodes {
178
+ reportNoImportAssignTarget(ctx, reportNode, element, imported, namespaces, allowBinding)
179
+ }
180
+ }
181
+
182
+ case shimast.KindObjectLiteralExpression:
183
+ if object := target.AsObjectLiteralExpression(); object != nil && object.Properties != nil {
184
+ for _, property := range object.Properties.Nodes {
185
+ reportNoImportAssignTarget(ctx, reportNode, property, imported, namespaces, allowBinding)
186
+ }
124
187
  }
188
+
189
+ case shimast.KindSpreadElement:
190
+ if spread := target.AsSpreadElement(); spread != nil {
191
+ reportNoImportAssignTarget(ctx, reportNode, spread.Expression, imported, namespaces, allowBinding)
192
+ }
193
+
194
+ case shimast.KindSpreadAssignment:
195
+ if spread := target.AsSpreadAssignment(); spread != nil {
196
+ reportNoImportAssignTarget(ctx, reportNode, spread.Expression, imported, namespaces, allowBinding)
197
+ }
198
+
199
+ case shimast.KindShorthandPropertyAssignment:
200
+ if property := target.AsShorthandPropertyAssignment(); property != nil {
201
+ reportNoImportAssignTarget(ctx, reportNode, property.Name(), imported, namespaces, allowBinding)
202
+ }
203
+
204
+ case shimast.KindPropertyAssignment:
205
+ if property := target.AsPropertyAssignment(); property != nil {
206
+ reportNoImportAssignTarget(ctx, reportNode, property.Initializer, imported, namespaces, allowBinding)
207
+ }
208
+
209
+ case shimast.KindBinaryExpression:
210
+ expression := target.AsBinaryExpression()
211
+ if expression != nil && expression.OperatorToken != nil &&
212
+ expression.OperatorToken.Kind == shimast.KindEqualsToken {
213
+ reportNoImportAssignTarget(ctx, reportNode, expression.Left, imported, namespaces, allowBinding)
214
+ }
215
+ }
216
+ }
217
+
218
+ func reportNoImportAssignMutationCall(
219
+ ctx *Context,
220
+ node *shimast.Node,
221
+ namespaces map[*shimast.Symbol]string,
222
+ ) {
223
+ call := node.AsCallExpression()
224
+ if call == nil || call.Arguments == nil || len(call.Arguments.Nodes) == 0 ||
225
+ !isNoImportAssignMutationFunction(ctx, call.Expression) {
125
226
  return
126
227
  }
127
- if stripped.Kind == shimast.KindPropertyAccessExpression {
128
- access := stripped.AsPropertyAccessExpression()
129
- if access == nil || access.Expression == nil {
130
- return
228
+ target := unwrapReferenceExpression(call.Arguments.Nodes[0])
229
+ if target == nil || target.Kind != shimast.KindIdentifier {
230
+ return
231
+ }
232
+ if symbol := noImportAssignValueSymbol(ctx, target); symbol != nil {
233
+ if name, ok := namespaces[symbol]; ok {
234
+ ctx.Report(node, "The members of '"+name+"' are read-only.")
235
+ }
236
+ }
237
+ }
238
+
239
+ func isNoImportAssignMutationFunction(ctx *Context, callee *shimast.Node) bool {
240
+ callee = unwrapReferenceExpression(callee)
241
+ if callee == nil {
242
+ return false
243
+ }
244
+ member, ok := referenceMemberParts(callee)
245
+ if !ok || member.staticKey == nil {
246
+ return false
247
+ }
248
+ object := unwrapReferenceExpression(member.object)
249
+ if object == nil || object.Kind != shimast.KindIdentifier {
250
+ return false
251
+ }
252
+ objectName := identifierText(object)
253
+ allowed := false
254
+ switch objectName {
255
+ case "Object":
256
+ switch *member.staticKey {
257
+ case "assign", "defineProperty", "defineProperties", "freeze", "setPrototypeOf":
258
+ allowed = true
131
259
  }
132
- if ns := identifierText(stripParens(access.Expression)); ns != "" && namespaces[ns] {
133
- ctx.Report(target, "The members of '"+ns+"' are read-only.")
260
+ case "Reflect":
261
+ switch *member.staticKey {
262
+ case "defineProperty", "deleteProperty", "set", "setPrototypeOf":
263
+ allowed = true
134
264
  }
135
265
  }
266
+ if !allowed {
267
+ return false
268
+ }
269
+
270
+ resolved := noImportAssignValueSymbol(ctx, object)
271
+ global := ctx.Checker.GetGlobalSymbol(objectName, shimast.SymbolFlagsValue, nil)
272
+ if global != nil {
273
+ global = ctx.Checker.GetMergedSymbol(global)
274
+ }
275
+ return resolved != nil && resolved == global
276
+ }
277
+
278
+ func noImportAssignDirectNamespaceName(
279
+ ctx *Context,
280
+ memberNode *shimast.Node,
281
+ namespaces map[*shimast.Symbol]string,
282
+ ) string {
283
+ member, ok := referenceMemberParts(memberNode)
284
+ if !ok {
285
+ return ""
286
+ }
287
+ receiver := unwrapReferenceExpression(member.object)
288
+ if receiver == nil || receiver.Kind != shimast.KindIdentifier {
289
+ return ""
290
+ }
291
+ return namespaces[noImportAssignValueSymbol(ctx, receiver)]
292
+ }
293
+
294
+ // noImportAssignValueSymbol resolves shorthand destructuring targets through
295
+ // their value slot, then normalizes merged declarations to a stable map key.
296
+ // A type-only import used illegally as a value has no value-position symbol;
297
+ // in that case the checker's all-meanings entity lookup still returns the
298
+ // lexical import alias without falling back to textual name matching.
299
+ func noImportAssignValueSymbol(ctx *Context, identifier *shimast.Node) *shimast.Symbol {
300
+ if ctx == nil || ctx.Checker == nil || identifier == nil {
301
+ return nil
302
+ }
303
+ var symbol *shimast.Symbol
304
+ shorthandTarget := false
305
+ if parent := identifier.Parent; parent != nil && parent.Kind == shimast.KindShorthandPropertyAssignment {
306
+ if shorthand := parent.AsShorthandPropertyAssignment(); shorthand != nil && shorthand.Name() == identifier {
307
+ shorthandTarget = true
308
+ symbol = ctx.Checker.GetShorthandAssignmentValueSymbol(parent)
309
+ }
310
+ }
311
+ if symbol == nil && !shorthandTarget {
312
+ symbol = ctx.Checker.GetSymbolAtLocation(identifier)
313
+ }
314
+ if symbol == nil {
315
+ symbol = shimchecker.Checker_resolveEntityName(
316
+ ctx.Checker,
317
+ identifier,
318
+ shimast.SymbolFlagsValue|shimast.SymbolFlagsType|shimast.SymbolFlagsNamespace,
319
+ true,
320
+ true,
321
+ nil,
322
+ )
323
+ }
324
+ if symbol == nil {
325
+ return nil
326
+ }
327
+ return ctx.Checker.GetMergedSymbol(symbol)
136
328
  }
137
329
 
138
330
  func init() {
@@ -0,0 +1,186 @@
1
+ package linthost
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+
7
+ shimast "github.com/microsoft/typescript-go/shim/ast"
8
+ shimcore "github.com/microsoft/typescript-go/shim/core"
9
+ )
10
+
11
+ // noInnerDeclarations enforces the root-declaration positions and option
12
+ // defaults of ESLint's core no-inner-declarations rule. TypeScript-Go parses
13
+ // JavaScript and TypeScript with the current ECMAScript grammar, so strict
14
+ // functions in ordinary parsed source have ES2015 block-function semantics.
15
+ // https://eslint.org/docs/latest/rules/no-inner-declarations
16
+ type noInnerDeclarations struct{ optionsRule }
17
+
18
+ type noInnerDeclarationsOptions struct {
19
+ both bool
20
+ allowBlockScopedFunc bool
21
+ }
22
+
23
+ type noInnerDeclarationsBlockOptions struct {
24
+ BlockScopedFunctions string `json:"blockScopedFunctions"`
25
+ }
26
+
27
+ func (noInnerDeclarations) Name() string { return "no-inner-declarations" }
28
+
29
+ func (noInnerDeclarations) Visits() []shimast.Kind {
30
+ return []shimast.Kind{
31
+ shimast.KindFunctionDeclaration,
32
+ shimast.KindVariableDeclarationList,
33
+ }
34
+ }
35
+
36
+ func (noInnerDeclarations) Check(ctx *Context, node *shimast.Node) {
37
+ options := resolveNoInnerDeclarationsOptions(ctx)
38
+ declaration := node
39
+ declarationType := "function"
40
+
41
+ if node.Kind == shimast.KindVariableDeclarationList {
42
+ if !options.both || !shimast.IsVar(node) {
43
+ return
44
+ }
45
+ declarationType = "variable"
46
+ // A statement-level list is wrapped by VariableStatement in the tsgo AST.
47
+ // Use that wrapper for root classification and for the diagnostic range;
48
+ // loop-header lists have no wrapper and remain nested declarations.
49
+ if node.Parent != nil && node.Parent.Kind == shimast.KindVariableStatement {
50
+ declaration = node.Parent
51
+ }
52
+ } else if options.allowBlockScopedFunc &&
53
+ noInnerDeclarationsSupportsBlockFunctions(ctx.File) &&
54
+ noInnerDeclarationsIsStrict(ctx.File, node) {
55
+ return
56
+ }
57
+
58
+ if noInnerDeclarationsIsRoot(declaration) {
59
+ return
60
+ }
61
+ ctx.Report(
62
+ declaration,
63
+ "Move "+declarationType+" declaration to "+noInnerDeclarationsAllowedBody(declaration)+" root.",
64
+ )
65
+ }
66
+
67
+ // resolveNoInnerDeclarationsOptions reads the canonical ESLint positional
68
+ // options. The config transport preserves one positional value directly and
69
+ // two or more values as an array, so both [severity, mode] and
70
+ // [severity, mode, object] arrive without rule-specific parser behavior.
71
+ func resolveNoInnerDeclarationsOptions(ctx *Context) noInnerDeclarationsOptions {
72
+ resolved := noInnerDeclarationsOptions{allowBlockScopedFunc: true}
73
+ if ctx == nil || len(ctx.Options) == 0 {
74
+ return resolved
75
+ }
76
+
77
+ raw := bytes.TrimSpace(ctx.Options)
78
+ if len(raw) == 0 {
79
+ return resolved
80
+ }
81
+ var slots []json.RawMessage
82
+ if raw[0] == '[' {
83
+ // Decode into a zero-value slice. Seeding it with raw would let the first
84
+ // RawMessage reuse and overwrite Context.Options' backing bytes, changing
85
+ // the option seen by every later declaration in the same file.
86
+ if err := json.Unmarshal(raw, &slots); err != nil {
87
+ return resolved
88
+ }
89
+ } else {
90
+ slots = []json.RawMessage{raw}
91
+ }
92
+
93
+ if len(slots) > 0 {
94
+ var mode string
95
+ if json.Unmarshal(slots[0], &mode) == nil && mode == "both" {
96
+ resolved.both = true
97
+ }
98
+ }
99
+ if len(slots) > 1 {
100
+ var options noInnerDeclarationsBlockOptions
101
+ if json.Unmarshal(slots[1], &options) == nil && options.BlockScopedFunctions == "disallow" {
102
+ resolved.allowBlockScopedFunc = false
103
+ }
104
+ }
105
+ return resolved
106
+ }
107
+
108
+ func noInnerDeclarationsSupportsBlockFunctions(file *shimast.SourceFile) bool {
109
+ return file != nil && file.ScriptKind != shimcore.ScriptKindJSON
110
+ }
111
+
112
+ // noInnerDeclarationsIsStrict derives strictness from parser-owned AST facts:
113
+ // external-module identity, class ancestry, and real directive prologues. It
114
+ // deliberately does not infer semantics from filenames or substring searches.
115
+ func noInnerDeclarationsIsStrict(file *shimast.SourceFile, node *shimast.Node) bool {
116
+ if file != nil && file.ExternalModuleIndicator != nil {
117
+ return true
118
+ }
119
+ for ancestor := node.Parent; ancestor != nil; ancestor = ancestor.Parent {
120
+ switch ancestor.Kind {
121
+ case shimast.KindClassDeclaration, shimast.KindClassExpression:
122
+ return true
123
+ case shimast.KindSourceFile, shimast.KindModuleBlock:
124
+ if noInnerDeclarationsHasUseStrictDirective(file, ancestor) {
125
+ return true
126
+ }
127
+ case shimast.KindBlock:
128
+ if isFunctionLikeKind(ancestor.Parent) && noInnerDeclarationsHasUseStrictDirective(file, ancestor) {
129
+ return true
130
+ }
131
+ }
132
+ }
133
+ return false
134
+ }
135
+
136
+ func noInnerDeclarationsHasUseStrictDirective(file *shimast.SourceFile, container *shimast.Node) bool {
137
+ for _, statement := range parentStatements(container) {
138
+ if statement == nil || statement.Kind != shimast.KindExpressionStatement {
139
+ return false
140
+ }
141
+ expressionStatement := statement.AsExpressionStatement()
142
+ if expressionStatement == nil || expressionStatement.Expression == nil ||
143
+ expressionStatement.Expression.Kind != shimast.KindStringLiteral {
144
+ return false
145
+ }
146
+ // ES5 forbids escapes in a Use Strict Directive. Match the parser/binder
147
+ // contract against the parser-classified literal token, not its cooked
148
+ // value (`"use\x20strict"` must stay sloppy).
149
+ raw := nodeText(file, expressionStatement.Expression)
150
+ if raw == `"use strict"` || raw == `'use strict'` {
151
+ return true
152
+ }
153
+ }
154
+ return false
155
+ }
156
+
157
+ func noInnerDeclarationsIsRoot(declaration *shimast.Node) bool {
158
+ if declaration == nil || declaration.Parent == nil {
159
+ return true
160
+ }
161
+ parent := declaration.Parent
162
+ switch parent.Kind {
163
+ case shimast.KindSourceFile, shimast.KindModuleBlock, shimast.KindClassStaticBlockDeclaration:
164
+ return true
165
+ case shimast.KindBlock:
166
+ return isFunctionLikeKind(parent.Parent) ||
167
+ (parent.Parent != nil && parent.Parent.Kind == shimast.KindClassStaticBlockDeclaration)
168
+ }
169
+ return false
170
+ }
171
+
172
+ func noInnerDeclarationsAllowedBody(declaration *shimast.Node) string {
173
+ for ancestor := declaration.Parent; ancestor != nil; ancestor = ancestor.Parent {
174
+ if ancestor.Kind == shimast.KindClassStaticBlockDeclaration {
175
+ return "class static block body"
176
+ }
177
+ if isFunctionLikeKind(ancestor) {
178
+ return "function body"
179
+ }
180
+ }
181
+ return "program"
182
+ }
183
+
184
+ func init() {
185
+ Register(noInnerDeclarations{})
186
+ }