@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,628 @@
1
+ package linthost
2
+
3
+ import (
4
+ "fmt"
5
+ "regexp"
6
+ "strconv"
7
+ "strings"
8
+ "unicode"
9
+ )
10
+
11
+ type astSelectorKind uint8
12
+
13
+ const (
14
+ astSelectorWildcard astSelectorKind = iota
15
+ astSelectorNodeType
16
+ astSelectorExactNode
17
+ astSelectorAttribute
18
+ astSelectorField
19
+ astSelectorMatches
20
+ astSelectorCompound
21
+ astSelectorNot
22
+ astSelectorHas
23
+ astSelectorChild
24
+ astSelectorDescendant
25
+ astSelectorSibling
26
+ astSelectorAdjacent
27
+ astSelectorNthChild
28
+ astSelectorNthLastChild
29
+ astSelectorClass
30
+ )
31
+
32
+ type astSelector struct {
33
+ kind astSelectorKind
34
+ name string
35
+ operator string
36
+ value astSelectorValue
37
+ selectors []*astSelector
38
+ left *astSelector
39
+ right *astSelector
40
+ index int
41
+ subject bool
42
+ }
43
+
44
+ type astSelectorValueKind uint8
45
+
46
+ const (
47
+ astSelectorValueMissing astSelectorValueKind = iota
48
+ astSelectorValueLiteral
49
+ astSelectorValueType
50
+ astSelectorValueRegexp
51
+ )
52
+
53
+ type astSelectorValue struct {
54
+ kind astSelectorValueKind
55
+ literal string
56
+ number *float64
57
+ regexp *regexp.Regexp
58
+ }
59
+
60
+ // parseASTSelector parses the selector language documented by esquery. The
61
+ // matcher runs over TypeScript-Go's AST rather than ESTree, but the grammar is
62
+ // intentionally the same: node types, attributes, fields, combinators,
63
+ // :not/:is/:matches/:has, child-position pseudos, classes, and subjects.
64
+ func parseASTSelector(source string) (*astSelector, error) {
65
+ parser := &astSelectorParser{source: source}
66
+ parser.skipSpace()
67
+ if parser.eof() {
68
+ return nil, fmt.Errorf("selector must not be empty")
69
+ }
70
+ selector, err := parser.parseSelectorList(0)
71
+ if err != nil {
72
+ return nil, err
73
+ }
74
+ parser.skipSpace()
75
+ if !parser.eof() {
76
+ return nil, parser.errorf("unexpected %q", parser.peek())
77
+ }
78
+ return selector, nil
79
+ }
80
+
81
+ type astSelectorParser struct {
82
+ source string
83
+ offset int
84
+ }
85
+
86
+ func (p *astSelectorParser) parseSelectorList(stop byte) (*astSelector, error) {
87
+ selectors := make([]*astSelector, 0, 1)
88
+ for {
89
+ p.skipSpace()
90
+ if p.eof() || stop != 0 && p.peek() == stop {
91
+ if len(selectors) == 0 {
92
+ return nil, p.errorf("expected selector")
93
+ }
94
+ break
95
+ }
96
+ selector, err := p.parseSelector()
97
+ if err != nil {
98
+ return nil, err
99
+ }
100
+ selectors = append(selectors, selector)
101
+ p.skipSpace()
102
+ if p.eof() || stop != 0 && p.peek() == stop {
103
+ break
104
+ }
105
+ if p.peek() != ',' {
106
+ return nil, p.errorf("expected ','")
107
+ }
108
+ p.offset++
109
+ p.skipSpace()
110
+ if p.eof() || stop != 0 && p.peek() == stop {
111
+ return nil, p.errorf("expected selector after ','")
112
+ }
113
+ }
114
+ if len(selectors) == 1 {
115
+ return selectors[0], nil
116
+ }
117
+ return &astSelector{kind: astSelectorMatches, selectors: selectors}, nil
118
+ }
119
+
120
+ func (p *astSelectorParser) parseSelector() (*astSelector, error) {
121
+ left, err := p.parseSequence()
122
+ if err != nil {
123
+ return nil, err
124
+ }
125
+ for {
126
+ spaced := p.skipSpace()
127
+ if p.eof() || p.peek() == ',' || p.peek() == ')' {
128
+ return left, nil
129
+ }
130
+ kind := astSelectorDescendant
131
+ switch p.peek() {
132
+ case '>':
133
+ kind = astSelectorChild
134
+ p.offset++
135
+ p.skipSpace()
136
+ case '~':
137
+ kind = astSelectorSibling
138
+ p.offset++
139
+ p.skipSpace()
140
+ case '+':
141
+ kind = astSelectorAdjacent
142
+ p.offset++
143
+ p.skipSpace()
144
+ default:
145
+ if !spaced {
146
+ return left, nil
147
+ }
148
+ }
149
+ right, err := p.parseSequence()
150
+ if err != nil {
151
+ return nil, err
152
+ }
153
+ left = &astSelector{kind: kind, left: left, right: right}
154
+ }
155
+ }
156
+
157
+ func (p *astSelectorParser) parseSequence() (*astSelector, error) {
158
+ subject := false
159
+ if !p.eof() && p.peek() == '!' {
160
+ subject = true
161
+ p.offset++
162
+ }
163
+ atoms := make([]*astSelector, 0, 2)
164
+ for !p.eof() {
165
+ switch p.peek() {
166
+ case ' ', '\t', '\r', '\n', ',', ')', '>', '~', '+':
167
+ goto done
168
+ }
169
+ atom, err := p.parseAtom()
170
+ if err != nil {
171
+ return nil, err
172
+ }
173
+ atoms = append(atoms, atom)
174
+ }
175
+
176
+ done:
177
+ if len(atoms) == 0 {
178
+ return nil, p.errorf("expected selector atom")
179
+ }
180
+ var result *astSelector
181
+ if len(atoms) == 1 {
182
+ result = atoms[0]
183
+ } else {
184
+ result = &astSelector{kind: astSelectorCompound, selectors: atoms}
185
+ }
186
+ result.subject = subject
187
+ return result, nil
188
+ }
189
+
190
+ func (p *astSelectorParser) parseAtom() (*astSelector, error) {
191
+ if p.eof() {
192
+ return nil, p.errorf("expected selector atom")
193
+ }
194
+ switch p.peek() {
195
+ case '*':
196
+ p.offset++
197
+ return &astSelector{kind: astSelectorWildcard}, nil
198
+ case '[':
199
+ return p.parseAttribute()
200
+ case '.':
201
+ return p.parseField()
202
+ case ':':
203
+ return p.parsePseudo()
204
+ case '#':
205
+ p.offset++
206
+ }
207
+ name := p.parseIdentifier()
208
+ if name == "" {
209
+ return nil, p.errorf("expected node type")
210
+ }
211
+ return &astSelector{kind: astSelectorNodeType, name: name}, nil
212
+ }
213
+
214
+ func (p *astSelectorParser) parseAttribute() (*astSelector, error) {
215
+ p.offset++
216
+ p.skipSpace()
217
+ name := p.parsePath()
218
+ if name == "" {
219
+ return nil, p.errorf("expected attribute name")
220
+ }
221
+ p.skipSpace()
222
+ if p.eof() {
223
+ return nil, p.errorf("unterminated attribute selector")
224
+ }
225
+ if p.peek() == ']' {
226
+ p.offset++
227
+ return &astSelector{kind: astSelectorAttribute, name: name}, nil
228
+ }
229
+ operator := ""
230
+ if strings.ContainsRune("!<>", rune(p.peek())) {
231
+ operator = string(p.peek())
232
+ p.offset++
233
+ }
234
+ if !p.eof() && p.peek() == '=' {
235
+ operator += "="
236
+ p.offset++
237
+ } else if operator == "!" {
238
+ return nil, p.errorf("expected '=' after '!'")
239
+ }
240
+ if operator == "" {
241
+ return nil, p.errorf("expected attribute operator")
242
+ }
243
+ p.skipSpace()
244
+ value, err := p.parseAttributeValue(operator)
245
+ if err != nil {
246
+ return nil, err
247
+ }
248
+ p.skipSpace()
249
+ if p.eof() || p.peek() != ']' {
250
+ return nil, p.errorf("expected ']'")
251
+ }
252
+ p.offset++
253
+ return &astSelector{
254
+ kind: astSelectorAttribute,
255
+ name: name,
256
+ operator: operator,
257
+ value: value,
258
+ }, nil
259
+ }
260
+
261
+ func (p *astSelectorParser) parseAttributeValue(operator string) (astSelectorValue, error) {
262
+ if p.eof() {
263
+ return astSelectorValue{}, p.errorf("expected attribute value")
264
+ }
265
+ if p.peek() == '\'' || p.peek() == '"' {
266
+ value, err := p.parseQuoted()
267
+ return astSelectorValue{kind: astSelectorValueLiteral, literal: value}, err
268
+ }
269
+ if p.peek() == '/' {
270
+ if operator != "=" && operator != "!=" {
271
+ return astSelectorValue{}, p.errorf("regular expressions require '=' or '!='")
272
+ }
273
+ compiled, err := p.parseRegexp()
274
+ return astSelectorValue{kind: astSelectorValueRegexp, regexp: compiled}, err
275
+ }
276
+ if strings.HasPrefix(p.source[p.offset:], "type(") {
277
+ if operator != "=" && operator != "!=" {
278
+ return astSelectorValue{}, p.errorf("type() requires '=' or '!='")
279
+ }
280
+ p.offset += len("type(")
281
+ p.skipSpace()
282
+ start := p.offset
283
+ for !p.eof() && !isASTSelectorSpace(p.peek()) && p.peek() != ')' {
284
+ p.offset++
285
+ }
286
+ name := p.source[start:p.offset]
287
+ p.skipSpace()
288
+ if name == "" || p.eof() || p.peek() != ')' {
289
+ return astSelectorValue{}, p.errorf("invalid type() value")
290
+ }
291
+ p.offset++
292
+ return astSelectorValue{kind: astSelectorValueType, literal: name}, nil
293
+ }
294
+ if p.peek() == '.' {
295
+ start := p.offset
296
+ p.offset++
297
+ for !p.eof() && p.peek() >= '0' && p.peek() <= '9' {
298
+ p.offset++
299
+ }
300
+ raw := p.source[start:p.offset]
301
+ if !astSelectorNumberPattern.MatchString(raw) {
302
+ return astSelectorValue{}, p.errorf("invalid attribute value")
303
+ }
304
+ number, _ := strconv.ParseFloat(raw, 64)
305
+ return astSelectorValue{kind: astSelectorValueLiteral, literal: raw, number: &number}, nil
306
+ }
307
+ if p.peek() >= '0' && p.peek() <= '9' {
308
+ start := p.offset
309
+ for !p.eof() && p.peek() >= '0' && p.peek() <= '9' {
310
+ p.offset++
311
+ }
312
+ if !p.eof() && p.peek() == '.' {
313
+ p.offset++
314
+ for !p.eof() && p.peek() >= '0' && p.peek() <= '9' {
315
+ p.offset++
316
+ }
317
+ raw := p.source[start:p.offset]
318
+ if !astSelectorNumberPattern.MatchString(raw) {
319
+ return astSelectorValue{}, p.errorf("invalid attribute value")
320
+ }
321
+ number, _ := strconv.ParseFloat(raw, 64)
322
+ return astSelectorValue{kind: astSelectorValueLiteral, literal: raw, number: &number}, nil
323
+ }
324
+ p.offset = start
325
+ }
326
+ raw := p.parseIdentifier()
327
+ if raw == "" {
328
+ return astSelectorValue{}, p.errorf("expected attribute value")
329
+ }
330
+ if astSelectorNumberPattern.MatchString(raw) {
331
+ number, _ := strconv.ParseFloat(raw, 64)
332
+ return astSelectorValue{kind: astSelectorValueLiteral, literal: raw, number: &number}, nil
333
+ }
334
+ return astSelectorValue{kind: astSelectorValueLiteral, literal: raw}, nil
335
+ }
336
+
337
+ func (p *astSelectorParser) parseQuoted() (string, error) {
338
+ quote := p.peek()
339
+ p.offset++
340
+ var out strings.Builder
341
+ for !p.eof() {
342
+ ch := p.peek()
343
+ p.offset++
344
+ if ch == quote {
345
+ return out.String(), nil
346
+ }
347
+ if ch != '\\' {
348
+ out.WriteByte(ch)
349
+ continue
350
+ }
351
+ if p.eof() {
352
+ return "", p.errorf("unterminated string escape")
353
+ }
354
+ escaped := p.peek()
355
+ p.offset++
356
+ switch escaped {
357
+ case 'b':
358
+ out.WriteByte('\b')
359
+ case 'f':
360
+ out.WriteByte('\f')
361
+ case 'n':
362
+ out.WriteByte('\n')
363
+ case 'r':
364
+ out.WriteByte('\r')
365
+ case 't':
366
+ out.WriteByte('\t')
367
+ case 'v':
368
+ out.WriteByte('\v')
369
+ default:
370
+ out.WriteByte(escaped)
371
+ }
372
+ }
373
+ return "", p.errorf("unterminated string")
374
+ }
375
+
376
+ func (p *astSelectorParser) parseRegexp() (*regexp.Regexp, error) {
377
+ p.offset++
378
+ start := p.offset
379
+ inClass := false
380
+ escaped := false
381
+ end := -1
382
+ for !p.eof() {
383
+ ch := p.peek()
384
+ p.offset++
385
+ if escaped {
386
+ escaped = false
387
+ continue
388
+ }
389
+ if ch == '\\' {
390
+ escaped = true
391
+ continue
392
+ }
393
+ if ch == '[' {
394
+ inClass = true
395
+ continue
396
+ }
397
+ if ch == ']' {
398
+ inClass = false
399
+ continue
400
+ }
401
+ if ch == '/' && !inClass {
402
+ end = p.offset - 1
403
+ break
404
+ }
405
+ }
406
+ if end < 0 {
407
+ return nil, p.errorf("unterminated regular expression")
408
+ }
409
+ if end == start {
410
+ return nil, p.errorf("regular expression must not be empty")
411
+ }
412
+ seenFlags := map[byte]struct{}{}
413
+ var goFlags strings.Builder
414
+ for !p.eof() {
415
+ flag := p.peek()
416
+ if !strings.ContainsRune("imsu", rune(flag)) {
417
+ break
418
+ }
419
+ p.offset++
420
+ if _, duplicate := seenFlags[flag]; duplicate {
421
+ return nil, p.errorf("duplicate regular-expression flag %q", flag)
422
+ }
423
+ seenFlags[flag] = struct{}{}
424
+ if flag != 'u' {
425
+ goFlags.WriteByte(flag)
426
+ }
427
+ }
428
+ pattern := p.source[start:end]
429
+ if goFlags.Len() != 0 {
430
+ pattern = "(?" + goFlags.String() + ":" + pattern + ")"
431
+ }
432
+ compiled, err := regexp.Compile(pattern)
433
+ if err != nil {
434
+ return nil, p.errorf("invalid regular expression: %v", err)
435
+ }
436
+ return compiled, nil
437
+ }
438
+
439
+ func (p *astSelectorParser) parseField() (*astSelector, error) {
440
+ p.offset++
441
+ name := p.parsePath()
442
+ if name == "" {
443
+ return nil, p.errorf("expected field name")
444
+ }
445
+ return &astSelector{kind: astSelectorField, name: name}, nil
446
+ }
447
+
448
+ func (p *astSelectorParser) parsePseudo() (*astSelector, error) {
449
+ p.offset++
450
+ name := p.parseIdentifier()
451
+ if name == "" {
452
+ return nil, p.errorf("expected pseudo-selector name")
453
+ }
454
+ switch name {
455
+ case "first-child":
456
+ return &astSelector{kind: astSelectorNthChild, index: 1}, nil
457
+ case "last-child":
458
+ return &astSelector{kind: astSelectorNthLastChild, index: 1}, nil
459
+ case "nth-child", "nth-last-child":
460
+ if p.eof() || p.peek() != '(' {
461
+ return nil, p.errorf("expected '('")
462
+ }
463
+ p.offset++
464
+ p.skipSpace()
465
+ start := p.offset
466
+ for !p.eof() && p.peek() >= '0' && p.peek() <= '9' {
467
+ p.offset++
468
+ }
469
+ if start == p.offset {
470
+ return nil, p.errorf("expected positive child index")
471
+ }
472
+ index, _ := strconv.Atoi(p.source[start:p.offset])
473
+ p.skipSpace()
474
+ if p.eof() || p.peek() != ')' {
475
+ return nil, p.errorf("expected ')'")
476
+ }
477
+ p.offset++
478
+ kind := astSelectorNthChild
479
+ if name == "nth-last-child" {
480
+ kind = astSelectorNthLastChild
481
+ }
482
+ return &astSelector{kind: kind, index: index}, nil
483
+ case "not", "matches", "is", "has":
484
+ if p.eof() || p.peek() != '(' {
485
+ return nil, p.errorf("expected '('")
486
+ }
487
+ p.offset++
488
+ p.skipSpace()
489
+ var nested *astSelector
490
+ var err error
491
+ if name == "has" {
492
+ nested, err = p.parseHasSelectorList()
493
+ } else {
494
+ nested, err = p.parseSelectorList(')')
495
+ }
496
+ if err != nil {
497
+ return nil, err
498
+ }
499
+ p.skipSpace()
500
+ if p.eof() || p.peek() != ')' {
501
+ return nil, p.errorf("expected ')'")
502
+ }
503
+ p.offset++
504
+ kind := astSelectorMatches
505
+ switch name {
506
+ case "not":
507
+ kind = astSelectorNot
508
+ case "has":
509
+ kind = astSelectorHas
510
+ }
511
+ selectors := nested.selectors
512
+ if nested.kind != astSelectorMatches {
513
+ selectors = []*astSelector{nested}
514
+ }
515
+ return &astSelector{kind: kind, selectors: selectors}, nil
516
+ default:
517
+ switch strings.ToLower(name) {
518
+ case "statement", "expression", "declaration", "function", "pattern":
519
+ return &astSelector{kind: astSelectorClass, name: name}, nil
520
+ default:
521
+ return nil, p.errorf("unknown AST class %q", name)
522
+ }
523
+ }
524
+ }
525
+
526
+ func (p *astSelectorParser) parseHasSelectorList() (*astSelector, error) {
527
+ selectors := make([]*astSelector, 0, 1)
528
+ for {
529
+ p.skipSpace()
530
+ if p.eof() || p.peek() == ')' {
531
+ if len(selectors) == 0 {
532
+ return nil, p.errorf("expected selector")
533
+ }
534
+ break
535
+ }
536
+ var leading astSelectorKind
537
+ switch p.peek() {
538
+ case '>':
539
+ leading = astSelectorChild
540
+ case '~':
541
+ leading = astSelectorSibling
542
+ case '+':
543
+ leading = astSelectorAdjacent
544
+ }
545
+ if leading != 0 {
546
+ p.offset++
547
+ p.skipSpace()
548
+ }
549
+ selector, err := p.parseSelector()
550
+ if err != nil {
551
+ return nil, err
552
+ }
553
+ if leading != 0 {
554
+ selector = &astSelector{
555
+ kind: leading,
556
+ left: &astSelector{kind: astSelectorExactNode},
557
+ right: selector,
558
+ }
559
+ }
560
+ selectors = append(selectors, selector)
561
+ p.skipSpace()
562
+ if p.eof() || p.peek() == ')' {
563
+ break
564
+ }
565
+ if p.peek() != ',' {
566
+ return nil, p.errorf("expected ','")
567
+ }
568
+ p.offset++
569
+ }
570
+ if len(selectors) == 1 {
571
+ return selectors[0], nil
572
+ }
573
+ return &astSelector{kind: astSelectorMatches, selectors: selectors}, nil
574
+ }
575
+
576
+ func (p *astSelectorParser) parsePath() string {
577
+ first := p.parseIdentifier()
578
+ if first == "" {
579
+ return ""
580
+ }
581
+ var path strings.Builder
582
+ path.WriteString(first)
583
+ for !p.eof() && p.peek() == '.' {
584
+ mark := p.offset
585
+ p.offset++
586
+ next := p.parseIdentifier()
587
+ if next == "" {
588
+ p.offset = mark
589
+ break
590
+ }
591
+ path.WriteByte('.')
592
+ path.WriteString(next)
593
+ }
594
+ return path.String()
595
+ }
596
+
597
+ func (p *astSelectorParser) parseIdentifier() string {
598
+ start := p.offset
599
+ for !p.eof() {
600
+ ch := p.peek()
601
+ if isASTSelectorSpace(ch) || strings.ContainsRune("[](),:#!=><~+.", rune(ch)) {
602
+ break
603
+ }
604
+ p.offset++
605
+ }
606
+ return p.source[start:p.offset]
607
+ }
608
+
609
+ func (p *astSelectorParser) skipSpace() bool {
610
+ start := p.offset
611
+ for !p.eof() && isASTSelectorSpace(p.peek()) {
612
+ p.offset++
613
+ }
614
+ return p.offset != start
615
+ }
616
+
617
+ func (p *astSelectorParser) eof() bool { return p.offset >= len(p.source) }
618
+ func (p *astSelectorParser) peek() byte { return p.source[p.offset] }
619
+
620
+ func (p *astSelectorParser) errorf(format string, args ...any) error {
621
+ return fmt.Errorf("selector byte %d: %s", p.offset+1, fmt.Sprintf(format, args...))
622
+ }
623
+
624
+ func isASTSelectorSpace(ch byte) bool {
625
+ return ch < unicode.MaxASCII && unicode.IsSpace(rune(ch))
626
+ }
627
+
628
+ var astSelectorNumberPattern = regexp.MustCompile(`^(?:[0-9]*\.)?[0-9]+$`)