@ttsc/lint 0.10.2 → 0.11.0-dev.20260517

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 (113) hide show
  1. package/README.md +172 -33
  2. package/lib/defineConfig.d.ts +55 -0
  3. package/lib/defineConfig.js +39 -0
  4. package/lib/defineConfig.js.map +1 -0
  5. package/lib/index.d.ts +25 -1
  6. package/lib/index.js +622 -2
  7. package/lib/index.js.map +1 -1
  8. package/lib/structures/ITtscLintPlugin.d.ts +53 -0
  9. package/lib/structures/ITtscLintPlugin.js +3 -0
  10. package/lib/structures/ITtscLintPlugin.js.map +1 -0
  11. package/lib/structures/ITtscLintPluginConfig.d.ts +82 -5
  12. package/lib/structures/ITtscLintPluginMeta.d.ts +12 -0
  13. package/lib/structures/ITtscLintPluginMeta.js +3 -0
  14. package/lib/structures/ITtscLintPluginMeta.js.map +1 -0
  15. package/lib/structures/PluginRuleNames.d.ts +14 -0
  16. package/lib/structures/PluginRuleNames.js +3 -0
  17. package/lib/structures/PluginRuleNames.js.map +1 -0
  18. package/lib/structures/TtscLintConfig.d.ts +24 -8
  19. package/lib/structures/TtscLintConfigEntry.d.ts +39 -0
  20. package/lib/structures/TtscLintConfigEntry.js +3 -0
  21. package/lib/structures/TtscLintConfigEntry.js.map +1 -0
  22. package/lib/structures/TtscLintFormatConfig.d.ts +154 -0
  23. package/lib/structures/TtscLintFormatConfig.js +3 -0
  24. package/lib/structures/TtscLintFormatConfig.js.map +1 -0
  25. package/lib/structures/TtscLintPlugins.d.ts +9 -0
  26. package/lib/structures/TtscLintPlugins.js +3 -0
  27. package/lib/structures/TtscLintPlugins.js.map +1 -0
  28. package/lib/structures/TtscLintRule.d.ts +1 -1
  29. package/lib/structures/TtscLintRuleEntry.d.ts +22 -0
  30. package/lib/structures/TtscLintRuleEntry.js +3 -0
  31. package/lib/structures/TtscLintRuleEntry.js.map +1 -0
  32. package/lib/structures/TtscLintRuleMap.d.ts +44 -0
  33. package/lib/structures/TtscLintRuleMap.js +3 -0
  34. package/lib/structures/TtscLintRuleMap.js.map +1 -0
  35. package/lib/structures/TtscLintRuleOptions.d.ts +149 -0
  36. package/lib/structures/TtscLintRuleOptions.js +3 -0
  37. package/lib/structures/TtscLintRuleOptions.js.map +1 -0
  38. package/lib/structures/defaultFormat.d.ts +31 -0
  39. package/lib/structures/defaultFormat.js +35 -0
  40. package/lib/structures/defaultFormat.js.map +1 -0
  41. package/lib/structures/index.d.ts +10 -0
  42. package/lib/structures/index.js +10 -0
  43. package/lib/structures/index.js.map +1 -1
  44. package/{plugin → linthost}/ast_helpers.go +92 -1
  45. package/{plugin → linthost}/compile.go +1 -1
  46. package/{plugin → linthost}/config.go +583 -66
  47. package/linthost/config_format.go +360 -0
  48. package/linthost/contrib_adapter.go +134 -0
  49. package/{plugin → linthost}/directives.go +1 -1
  50. package/linthost/dispatch.go +62 -0
  51. package/{plugin → linthost}/engine.go +156 -11
  52. package/{plugin → linthost}/eslint_runtime.go +76 -2
  53. package/linthost/fix.go +249 -0
  54. package/linthost/format.go +107 -0
  55. package/{plugin → linthost}/host.go +1 -1
  56. package/linthost/print_dispatch.go +118 -0
  57. package/linthost/print_doc.go +168 -0
  58. package/linthost/print_engine.go +343 -0
  59. package/linthost/print_nodes_array.go +46 -0
  60. package/linthost/print_nodes_call.go +180 -0
  61. package/linthost/print_nodes_imports.go +171 -0
  62. package/linthost/print_nodes_list.go +64 -0
  63. package/linthost/print_nodes_object.go +51 -0
  64. package/{plugin → linthost}/rules_arrays.go +1 -1
  65. package/{plugin → linthost}/rules_console.go +1 -1
  66. package/{plugin → linthost}/rules_debugger.go +1 -1
  67. package/{plugin → linthost}/rules_dupes.go +1 -1
  68. package/{plugin → linthost}/rules_empty.go +1 -1
  69. package/linthost/rules_escape.go +238 -0
  70. package/{plugin → linthost}/rules_eval.go +1 -1
  71. package/{plugin → linthost}/rules_finally.go +1 -1
  72. package/linthost/rules_format_jsdoc.go +230 -0
  73. package/linthost/rules_format_print_width.go +332 -0
  74. package/linthost/rules_format_quotes.go +219 -0
  75. package/linthost/rules_format_semi.go +190 -0
  76. package/linthost/rules_format_sort_imports.go +462 -0
  77. package/linthost/rules_format_trailing_comma.go +460 -0
  78. package/{plugin → linthost}/rules_gap.go +101 -6
  79. package/linthost/rules_imports.go +101 -0
  80. package/{plugin → linthost}/rules_logic.go +55 -3
  81. package/{plugin → linthost}/rules_loops.go +1 -1
  82. package/{plugin → linthost}/rules_misc.go +1 -1
  83. package/linthost/rules_params.go +64 -0
  84. package/{plugin → linthost}/rules_problems.go +10 -2
  85. package/linthost/rules_promise.go +111 -0
  86. package/{plugin → linthost}/rules_protos.go +1 -1
  87. package/{plugin → linthost}/rules_self.go +1 -1
  88. package/{plugin → linthost}/rules_strings.go +1 -1
  89. package/{plugin → linthost}/rules_suggestions.go +51 -9
  90. package/{plugin → linthost}/rules_throw.go +1 -1
  91. package/{plugin → linthost}/rules_ts.go +12 -2
  92. package/{plugin → linthost}/rules_ts_extra.go +90 -4
  93. package/{plugin → linthost}/rules_var.go +36 -5
  94. package/package.json +8 -6
  95. package/plugin/main.go +14 -27
  96. package/rule/astutil/astutil.go +148 -0
  97. package/rule/rule.go +291 -0
  98. package/src/defineConfig.ts +69 -0
  99. package/src/index.ts +807 -3
  100. package/src/structures/ITtscLintPlugin.ts +56 -0
  101. package/src/structures/ITtscLintPluginConfig.ts +86 -5
  102. package/src/structures/ITtscLintPluginMeta.ts +14 -0
  103. package/src/structures/PluginRuleNames.ts +19 -0
  104. package/src/structures/TtscLintConfig.ts +27 -8
  105. package/src/structures/TtscLintConfigEntry.ts +50 -0
  106. package/src/structures/TtscLintFormatConfig.ts +169 -0
  107. package/src/structures/TtscLintPlugins.ts +10 -0
  108. package/src/structures/TtscLintRule.ts +14 -1
  109. package/src/structures/TtscLintRuleEntry.ts +28 -0
  110. package/src/structures/TtscLintRuleMap.ts +52 -0
  111. package/src/structures/TtscLintRuleOptions.ts +162 -0
  112. package/src/structures/defaultFormat.ts +33 -0
  113. package/src/structures/index.ts +10 -0
@@ -0,0 +1,171 @@
1
+ package linthost
2
+
3
+ import (
4
+ shimast "github.com/microsoft/typescript-go/shim/ast"
5
+ )
6
+
7
+ // printNamedImports renders `{ a, b, c }` inside an import declaration.
8
+ // Each specifier flows through PrintNode, so `a as b` shapes that the
9
+ // dispatcher does not understand yet still print verbatim.
10
+ //
11
+ // Flat: `{ a, b, c }`
12
+ //
13
+ // Broken: `{
14
+ // a,
15
+ // b,
16
+ // c,
17
+ // }`
18
+ //
19
+ // The open/close braces always carry a single space inside in flat
20
+ // mode, matching Prettier's `bracketSpacing: true` default for import
21
+ // declarations (which is hard-coded; Prettier ignores `bracketSpacing`
22
+ // for imports). Empty named imports `{}` collapse cleanly.
23
+ func printNamedImports(ctx *PrintContext, node *shimast.Node) Doc {
24
+ if node == nil {
25
+ return Doc{}
26
+ }
27
+ ni := node.AsNamedImports()
28
+ if ni == nil || ni.Elements == nil {
29
+ return verbatim(ctx, node)
30
+ }
31
+ items := make([]Doc, 0, len(ni.Elements.Nodes))
32
+ for _, spec := range ni.Elements.Nodes {
33
+ if spec == nil {
34
+ return verbatim(ctx, node)
35
+ }
36
+ doc, _ := PrintNode(ctx, spec)
37
+ items = append(items, doc)
38
+ }
39
+ return printList(ctx, listShape{
40
+ OpenTok: "{",
41
+ CloseTok: "}",
42
+ Items: items,
43
+ Space: true,
44
+ AddComma: true,
45
+ })
46
+ }
47
+
48
+ // printNamedExports renders `export { a, b }`. The shape is identical
49
+ // to NamedImports; only the surrounding declaration differs.
50
+ func printNamedExports(ctx *PrintContext, node *shimast.Node) Doc {
51
+ if node == nil {
52
+ return Doc{}
53
+ }
54
+ ne := node.AsNamedExports()
55
+ if ne == nil || ne.Elements == nil {
56
+ return verbatim(ctx, node)
57
+ }
58
+ items := make([]Doc, 0, len(ne.Elements.Nodes))
59
+ for _, spec := range ne.Elements.Nodes {
60
+ if spec == nil {
61
+ return verbatim(ctx, node)
62
+ }
63
+ doc, _ := PrintNode(ctx, spec)
64
+ items = append(items, doc)
65
+ }
66
+ return printList(ctx, listShape{
67
+ OpenTok: "{",
68
+ CloseTok: "}",
69
+ Items: items,
70
+ Space: true,
71
+ AddComma: true,
72
+ })
73
+ }
74
+
75
+ // printImportDeclaration renders the surrounding `import … from "x";`.
76
+ // The default specifier, namespace import, named-imports clause, and
77
+ // module specifier are all individually dispatched so the named-imports
78
+ // portion can reflow while the rest stays verbatim.
79
+ //
80
+ // The dispatcher hands off to the per-clause printers; the top-level
81
+ // frame stitches them together with the keywords and `from` token.
82
+ func printImportDeclaration(ctx *PrintContext, node *shimast.Node) Doc {
83
+ if node == nil {
84
+ return Doc{}
85
+ }
86
+ imp := node.AsImportDeclaration()
87
+ if imp == nil {
88
+ return verbatim(ctx, node)
89
+ }
90
+ // If the declaration uses anything other than a vanilla
91
+ // `import { ... } from "x"` shape (default specifier, namespace
92
+ // alias, attributes clause, etc.), fall back to verbatim. The
93
+ // canonical reflow target is the named-import body.
94
+ clause := imp.ImportClause
95
+ if clause == nil {
96
+ return verbatim(ctx, node)
97
+ }
98
+ clauseData := clause.AsImportClause()
99
+ if clauseData == nil || clauseData.NamedBindings == nil {
100
+ return verbatim(ctx, node)
101
+ }
102
+ if clauseData.NamedBindings.Kind != shimast.KindNamedImports {
103
+ // Namespace imports (`import * as ns from "x"`) have no
104
+ // reflow surface; leave them alone.
105
+ return verbatim(ctx, node)
106
+ }
107
+ if clause.Name() != nil {
108
+ // `import Default, { … } from "x"` — keep verbatim for v1.
109
+ return verbatim(ctx, node)
110
+ }
111
+
112
+ // Bracketed clause prefix: `import ` (and optional `type `).
113
+ prefix := "import "
114
+ if clause.IsTypeOnly() {
115
+ prefix = "import type "
116
+ }
117
+ named, _ := PrintNode(ctx, clauseData.NamedBindings)
118
+ moduleSpec := verbatim(ctx, imp.ModuleSpecifier)
119
+ parts := []Doc{Text(prefix), named, Text(" from "), moduleSpec}
120
+ if sourceHasStatementTerminator(ctx.Source, node.End()) {
121
+ // Preserve the user's terminator decision. Emitting `;`
122
+ // unconditionally would collide with `format/semi`'s
123
+ // zero-width insert on the same cascade pass and produce
124
+ // `;;` — `format/semi` owns terminator placement.
125
+ parts = append(parts, Text(";"))
126
+ }
127
+ // AttributeClause (`with { ... }` / `assert { ... }`) lives after
128
+ // the module specifier. Fall back to verbatim when present so we
129
+ // don't drop attributes silently.
130
+ if imp.Attributes != nil {
131
+ return verbatim(ctx, node)
132
+ }
133
+ return Concat(parts...)
134
+ }
135
+
136
+ // sourceHasStatementTerminator reports whether the last non-trivia
137
+ // byte at or before `end` is `;`. TypeScript-Go's parser folds an
138
+ // optional trailing semicolon into the statement's End(), so this
139
+ // check tells the printer whether the user wrote one.
140
+ //
141
+ // Trailing block comments (`import { … } from "x" /* tail */;`) sit
142
+ // between the module specifier and the `;`. The backward walk must
143
+ // step past them; otherwise the helper sees `/` from `*/` and
144
+ // concludes "no terminator".
145
+ func sourceHasStatementTerminator(src string, end int) bool {
146
+ i := end - 1
147
+ for i >= 0 {
148
+ c := src[i]
149
+ if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
150
+ i--
151
+ continue
152
+ }
153
+ if c == ';' {
154
+ return true
155
+ }
156
+ // Skip a trailing block comment: walk back past `*/`...`/*`.
157
+ if c == '/' && i-1 >= 0 && src[i-1] == '*' {
158
+ j := i - 2
159
+ for j-1 >= 0 && !(src[j-1] == '/' && src[j] == '*') {
160
+ j--
161
+ }
162
+ if j-1 < 0 {
163
+ return false
164
+ }
165
+ i = j - 2
166
+ continue
167
+ }
168
+ return false
169
+ }
170
+ return false
171
+ }
@@ -0,0 +1,64 @@
1
+ package linthost
2
+
3
+ // Shared helpers for comma-separated list printers.
4
+ //
5
+ // Object literals, array literals, call arguments, parameter lists,
6
+ // named imports, named exports, tuple types, type-parameter lists —
7
+ // they all share the same broken/flat shape:
8
+ //
9
+ // flat: OPEN child, child, child CLOSE
10
+ // broken: OPEN
11
+ // child,
12
+ // child,
13
+ // child, // optional trailing comma
14
+ // CLOSE
15
+ //
16
+ // printList captures that shape in one place so every per-node printer
17
+ // can stay tiny.
18
+ //
19
+ // `openTok` and `closeTok` are the literal punctuation strings; the
20
+ // helper does not look them up from the source because that lookup
21
+ // would couple every list to its own scanner. `space` toggles the
22
+ // "single space between OPEN and the first child" used by object
23
+ // literals (`{ a }`) but not arrays (`[a]`).
24
+ type listShape struct {
25
+ OpenTok string
26
+ CloseTok string
27
+ Items []Doc
28
+ Space bool // emit a space after OPEN / before CLOSE in flat mode
29
+ AddComma bool // emit a trailing comma in broken mode
30
+ }
31
+
32
+ // printList renders the list shape as a Doc tree. Empty lists collapse
33
+ // to `OPENCLOSE`. Single-element lists still wrap so trailing-comma
34
+ // logic stays uniform.
35
+ func printList(ctx *PrintContext, shape listShape) Doc {
36
+ if len(shape.Items) == 0 {
37
+ return Text(shape.OpenTok + shape.CloseTok)
38
+ }
39
+ sep := Concat(Text(","), Line())
40
+ body := Join(sep, shape.Items)
41
+
42
+ flatPad := Doc{Kind: docNil}
43
+ if shape.Space {
44
+ flatPad = Text(" ")
45
+ }
46
+
47
+ trailing := Doc{Kind: docNil}
48
+ if shape.AddComma {
49
+ trailing = IfBreak(Text(","), Doc{Kind: docNil})
50
+ }
51
+
52
+ openTok := Text(shape.OpenTok)
53
+ closeTok := Text(shape.CloseTok)
54
+
55
+ // In flat mode: OPEN [pad] body [pad] CLOSE
56
+ // In broken mode: OPEN \n body, \n CLOSE — using Softline so the
57
+ // flat form collapses cleanly.
58
+ leadingSep := IfBreak(Hardline(), flatPad)
59
+ trailingSep := IfBreak(Hardline(), flatPad)
60
+
61
+ bodyBlock := Indent(ctx.indentUnit(), leadingSep, body, trailing)
62
+ doc := Concat(openTok, bodyBlock, trailingSep, closeTok)
63
+ return Group(doc)
64
+ }
@@ -0,0 +1,51 @@
1
+ package linthost
2
+
3
+ import (
4
+ shimast "github.com/microsoft/typescript-go/shim/ast"
5
+ )
6
+
7
+ // printObjectLiteral renders an ObjectLiteralExpression with width-aware
8
+ // fit-or-break. Members are emitted verbatim from the source — only
9
+ // the outer braces and the comma layout participate in reflow. That
10
+ // keeps the per-member behavior compatible with shapes the dispatcher
11
+ // has not yet learned (computed property keys, accessor declarations,
12
+ // spread elements, etc.), while still solving the headline use case:
13
+ // long member lists that need to break across lines.
14
+ //
15
+ // Flat: `{ a: 1, b: 2 }`
16
+ //
17
+ // Broken: `{
18
+ // a: 1,
19
+ // b: 2,
20
+ // }`
21
+ //
22
+ // The flat form uses a single space inside the braces, matching
23
+ // Prettier's `bracketSpacing: true` default. Empty object literals
24
+ // collapse to `{}` with no inner space, matching every formatter.
25
+ func printObjectLiteral(ctx *PrintContext, node *shimast.Node) Doc {
26
+ if node == nil {
27
+ return Doc{}
28
+ }
29
+ obj := node.AsObjectLiteralExpression()
30
+ if obj == nil || obj.Properties == nil {
31
+ return verbatim(ctx, node)
32
+ }
33
+ items := make([]Doc, 0, len(obj.Properties.Nodes))
34
+ for _, prop := range obj.Properties.Nodes {
35
+ if prop == nil {
36
+ // A nil child entry would render as an empty Doc and surface
37
+ // as `a, , b` in the output. Bail to verbatim so the source
38
+ // bytes round-trip unchanged.
39
+ return verbatim(ctx, node)
40
+ }
41
+ doc, _ := PrintNode(ctx, prop)
42
+ items = append(items, doc)
43
+ }
44
+ return printList(ctx, listShape{
45
+ OpenTok: "{",
46
+ CloseTok: "}",
47
+ Items: items,
48
+ Space: true,
49
+ AddComma: true,
50
+ })
51
+ }
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4
 
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4
 
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4
 
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4
 
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4
 
@@ -0,0 +1,238 @@
1
+ package linthost
2
+
3
+ import (
4
+ "strings"
5
+
6
+ shimast "github.com/microsoft/typescript-go/shim/ast"
7
+ )
8
+
9
+ // no-useless-escape: flag backslashes that escape characters which do not
10
+ // require escaping. Inside string/template literals the meaningful
11
+ // escapes are limited to a fixed set; everywhere else the backslash is
12
+ // noise from a copy/paste accident or a confused author. ESLint
13
+ // canonical: https://eslint.org/docs/latest/rules/no-useless-escape
14
+ //
15
+ // The rule is `eslint:recommended` and ships a one-byte autofix that
16
+ // deletes the redundant backslash. We mirror that fixer when the byte
17
+ // after the backslash is ASCII; multi-byte UTF-8 sequences are left
18
+ // detection-only because byte-deletion in the middle of a code point
19
+ // would corrupt the file.
20
+ type noUselessEscape struct{}
21
+
22
+ func (noUselessEscape) Name() string { return "no-useless-escape" }
23
+ func (noUselessEscape) Visits() []shimast.Kind {
24
+ return []shimast.Kind{
25
+ shimast.KindStringLiteral,
26
+ shimast.KindNoSubstitutionTemplateLiteral,
27
+ shimast.KindTemplateHead,
28
+ shimast.KindTemplateMiddle,
29
+ shimast.KindTemplateTail,
30
+ shimast.KindRegularExpressionLiteral,
31
+ }
32
+ }
33
+ func (noUselessEscape) Check(ctx *Context, node *shimast.Node) {
34
+ if ctx.File == nil {
35
+ return
36
+ }
37
+ // Tagged templates expose the raw bytes of the template to the tag
38
+ // function (`String.raw`, `dedent`, `gql`, `css`, …), so a backslash
39
+ // that looks redundant to the JS lexer is meaningful at the tag
40
+ // boundary. ESLint canonical skips tagged templates entirely.
41
+ if isInsideTaggedTemplate(node) {
42
+ return
43
+ }
44
+ src := ctx.File.Text()
45
+ pos := node.Pos()
46
+ end := node.End()
47
+ if pos < 0 || end > len(src) || pos >= end {
48
+ return
49
+ }
50
+ raw := src[pos:end]
51
+ // Determine quote/scan delimiters based on node kind so the
52
+ // single-char escape whitelist matches ESLint per-context.
53
+ switch node.Kind {
54
+ case shimast.KindStringLiteral:
55
+ reportStringEscapes(ctx, raw, pos, stringValidEscapes)
56
+ case shimast.KindNoSubstitutionTemplateLiteral,
57
+ shimast.KindTemplateHead,
58
+ shimast.KindTemplateMiddle,
59
+ shimast.KindTemplateTail:
60
+ reportStringEscapes(ctx, raw, pos, templateValidEscapes)
61
+ case shimast.KindRegularExpressionLiteral:
62
+ reportRegexEscapes(ctx, raw, pos)
63
+ }
64
+ }
65
+
66
+ const stringValidEscapes = "'\"\\bfnrtv0xuU\n\r"
67
+ const templateValidEscapes = "`'\"\\bfnrtv0xuU$\n\r"
68
+
69
+ // regexValidEscapes covers characters that are *always* meaningful when
70
+ // preceded by `\` in a regex pattern. Character classes and group syntax
71
+ // inside a regex add more legitimate escapes (`\d`, `\w`, …); we handle
72
+ // those by allowing every ASCII letter (which makes the fix conservative
73
+ // — it never deletes a backslash whose meaning could be context-sensitive).
74
+ const regexValidEscapes = "^$\\.*+?()[]{}|/-\n\r"
75
+
76
+ func reportStringEscapes(ctx *Context, raw string, base int, whitelist string) {
77
+ if len(raw) < 2 {
78
+ return
79
+ }
80
+ // Strip the enclosing quotes; first and last bytes are quote / backtick / `${` / `}`.
81
+ // For TemplateHead/Middle/Tail the head/tail use backtick + `${`, but
82
+ // every quoted form starts with one ASCII byte and ends with one or
83
+ // two ASCII bytes — neither contains a meaningful backslash, so just
84
+ // skip the first byte and stop one byte before the end.
85
+ startSkip := 1
86
+ endSkip := 1
87
+ if raw[len(raw)-2] == '$' && raw[len(raw)-1] == '{' {
88
+ endSkip = 2
89
+ }
90
+ for i := startSkip; i < len(raw)-endSkip; i++ {
91
+ if raw[i] != '\\' {
92
+ continue
93
+ }
94
+ if i+1 >= len(raw)-endSkip {
95
+ return
96
+ }
97
+ next := raw[i+1]
98
+ if isUselessStringEscape(next, whitelist) {
99
+ // Only emit a fix when both surrounding bytes are plain ASCII so
100
+ // deleting one byte cannot corrupt a multi-byte sequence.
101
+ if next < 0x80 {
102
+ ctx.ReportRangeFix(
103
+ base+i,
104
+ base+i+1,
105
+ "Unnecessary escape character: \\"+string(next)+".",
106
+ TextEdit{Pos: base + i, End: base + i + 1, Text: ""},
107
+ )
108
+ } else {
109
+ ctx.ReportRange(
110
+ base+i,
111
+ base+i+1,
112
+ "Unnecessary escape character: \\"+string(next)+".",
113
+ )
114
+ }
115
+ }
116
+ i++ // skip the escaped char so `\\\\` doesn't double-report.
117
+ }
118
+ }
119
+
120
+ func reportRegexEscapes(ctx *Context, raw string, base int) {
121
+ if len(raw) < 3 || raw[0] != '/' {
122
+ return
123
+ }
124
+ // The regex pattern is between the leading `/` and the trailing
125
+ // `/<flags>`; locate the closing slash by scanning right-to-left.
126
+ closing := strings.LastIndexByte(raw, '/')
127
+ if closing <= 0 {
128
+ return
129
+ }
130
+ body := raw[1:closing]
131
+ inClass := false
132
+ for i := 0; i < len(body); i++ {
133
+ ch := body[i]
134
+ if ch == '[' && !inClass {
135
+ inClass = true
136
+ continue
137
+ }
138
+ if ch == ']' && inClass {
139
+ inClass = false
140
+ continue
141
+ }
142
+ if ch != '\\' || i+1 >= len(body) {
143
+ continue
144
+ }
145
+ next := body[i+1]
146
+ if isUselessRegexEscape(next, inClass) {
147
+ // base + 1 (for the leading `/`) + i is the byte offset of the
148
+ // backslash in the source.
149
+ pos := base + 1 + i
150
+ if next < 0x80 {
151
+ ctx.ReportRangeFix(
152
+ pos,
153
+ pos+1,
154
+ "Unnecessary escape character: \\"+string(next)+".",
155
+ TextEdit{Pos: pos, End: pos + 1, Text: ""},
156
+ )
157
+ } else {
158
+ ctx.ReportRange(
159
+ pos,
160
+ pos+1,
161
+ "Unnecessary escape character: \\"+string(next)+".",
162
+ )
163
+ }
164
+ }
165
+ i++ // consume the escaped character.
166
+ }
167
+ }
168
+
169
+ func isUselessStringEscape(ch byte, whitelist string) bool {
170
+ // Whitespace + control chars are escape sequences too.
171
+ if ch < 0x20 {
172
+ return false
173
+ }
174
+ if strings.IndexByte(whitelist, ch) >= 0 {
175
+ return false
176
+ }
177
+ // ASCII letters that aren't in the whitelist are user-error escapes
178
+ // like `\a`, `\m`. Digits 1-9 are octal-shaped (`no-octal-escape`
179
+ // owns those). `0` is in the whitelist. Punctuation that isn't in the
180
+ // whitelist (e.g., `\.`) is also redundant.
181
+ return true
182
+ }
183
+
184
+ func isUselessRegexEscape(ch byte, inClass bool) bool {
185
+ if ch < 0x20 {
186
+ return false
187
+ }
188
+ // Regex meta-chars and their friends.
189
+ if strings.IndexByte(regexValidEscapes, ch) >= 0 {
190
+ return false
191
+ }
192
+ // Inside a character class `]`, `\`, and `-` are still meaningful
193
+ // beyond the always-list; everything else stays useless.
194
+ if inClass {
195
+ switch ch {
196
+ case ']', '\\', '-', 'b':
197
+ return false
198
+ }
199
+ }
200
+ // Common regex shorthand: \d \D \w \W \s \S \b \B \f \n \r \t \v \0
201
+ switch ch {
202
+ case 'd', 'D', 'w', 'W', 's', 'S', 'b', 'B', 'f', 'n', 'r', 't', 'v', '0',
203
+ 'x', 'u', 'c', 'p', 'P', 'k', 'q':
204
+ return false
205
+ }
206
+ return true
207
+ }
208
+
209
+ // isInsideTaggedTemplate reports whether `node` is the template payload of
210
+ // a TaggedTemplateExpression. Two AST shapes reach here:
211
+ //
212
+ // - `KindNoSubstitutionTemplateLiteral` — the direct child of a
213
+ // TaggedTemplateExpression's Template slot.
214
+ // - `KindTemplateHead/Middle/Tail` — wrapped in TemplateSpan and
215
+ // TemplateExpression nodes; the TemplateExpression's parent is the
216
+ // TaggedTemplateExpression.
217
+ func isInsideTaggedTemplate(node *shimast.Node) bool {
218
+ if node == nil || node.Parent == nil {
219
+ return false
220
+ }
221
+ parent := node.Parent
222
+ if parent.Kind == shimast.KindTaggedTemplateExpression {
223
+ return true
224
+ }
225
+ // Walk up at most two more hops to reach the TaggedTemplateExpression
226
+ // for the spans family.
227
+ for i := 0; i < 2 && parent.Parent != nil; i++ {
228
+ parent = parent.Parent
229
+ if parent.Kind == shimast.KindTaggedTemplateExpression {
230
+ return true
231
+ }
232
+ }
233
+ return false
234
+ }
235
+
236
+ func init() {
237
+ Register(noUselessEscape{})
238
+ }
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4
 
@@ -1,4 +1,4 @@
1
- package main
1
+ package linthost
2
2
 
3
3
  import shimast "github.com/microsoft/typescript-go/shim/ast"
4
4