@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.
- package/README.md +95 -43
- package/go.mod +5 -4
- package/internal/rulecode/rulecode.go +88 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +3 -0
- package/lib/structures/TtscLintRuleSetting.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormat.d.ts +3 -2
- package/lib/structures/format/ITtscLintFormatSortImports.d.ts +20 -4
- package/lib/structures/rules/ITtscLintBoundariesRuleOptions.d.ts +80 -8
- package/lib/structures/rules/ITtscLintBoundariesRules.d.ts +6 -8
- package/lib/structures/rules/ITtscLintContributorRules.d.ts +18 -2
- package/lib/structures/rules/ITtscLintCoreRuleOptions.d.ts +347 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintCoreRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintCoreRules.d.ts +101 -67
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +7 -2
- package/lib/structures/rules/ITtscLintRuleOptionsMap.d.ts +53 -7
- package/lib/structures/rules/ITtscLintRules.d.ts +6 -2
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +0 -8
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +206 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js +8 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +55 -42
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.d.ts +276 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js +3 -0
- package/lib/structures/rules/ITtscLintUnicornRuleOptions.js.map +1 -0
- package/lib/structures/rules/ITtscLintUnicornRules.d.ts +44 -36
- package/lib/structures/rules/index.d.ts +3 -0
- package/lib/structures/rules/index.js +3 -0
- package/lib/structures/rules/index.js.map +1 -1
- package/linthost/ast_helpers.go +521 -23
- package/linthost/ast_selector_matcher.go +1628 -0
- package/linthost/ast_selector_parser.go +628 -0
- package/linthost/comment_scan.go +154 -0
- package/linthost/compile.go +129 -79
- package/linthost/config.go +352 -88
- package/linthost/config_format.go +15 -1
- package/linthost/contrib_adapter.go +47 -8
- package/linthost/declaration_rules.go +12 -11
- package/linthost/directives.go +9 -67
- package/linthost/dispatch.go +37 -3
- package/linthost/engine.go +368 -53
- package/linthost/escape_string.go +77 -0
- package/linthost/fix.go +133 -23
- package/linthost/flags_gen.go +18 -17
- package/linthost/format.go +73 -167
- package/linthost/format_editor_settings.go +134 -26
- package/linthost/grapheme_tables_gen.go +1920 -0
- package/linthost/host.go +160 -61
- package/linthost/literal_escape_scan.go +117 -0
- package/linthost/lsp.go +545 -98
- package/linthost/numeric_literal_precision.go +228 -0
- package/linthost/polyfill_browserslist.go +1439 -0
- package/linthost/polyfill_browserslist_config.go +603 -0
- package/linthost/polyfill_corejs.go +750 -0
- package/linthost/polyfill_data.go +113 -0
- package/linthost/polyfill_data_gen.json +39868 -0
- package/linthost/print_nodes_array.go +7 -1
- package/linthost/print_nodes_object.go +7 -1
- package/linthost/project_engine.go +151 -0
- package/linthost/project_rules.go +78 -0
- package/linthost/regex_clean.go +191 -0
- package/linthost/regex_tree.go +1544 -0
- package/linthost/regex_tree_optimizer.go +1685 -0
- package/linthost/regexp_cache.go +44 -0
- package/linthost/rule_codes.go +105 -0
- package/linthost/rule_codes.json +745 -0
- package/linthost/rules_ban_ts_comment.go +312 -0
- package/linthost/rules_boundaries.go +128 -43
- package/linthost/rules_boundaries_dependencies.go +1013 -0
- package/linthost/rules_core_extra.go +62 -92
- package/linthost/rules_cypress.go +1 -1
- package/linthost/rules_default_case.go +87 -11
- package/linthost/rules_dupes.go +38 -13
- package/linthost/rules_empty.go +188 -20
- package/linthost/rules_escape.go +62 -48
- package/linthost/rules_format_arrow_parens.go +19 -5
- package/linthost/rules_format_bracket_spacing.go +1 -1
- package/linthost/rules_format_clause_join.go +1 -1
- package/linthost/rules_format_declaration_header.go +31 -19
- package/linthost/rules_format_indent.go +1 -1
- package/linthost/rules_format_jsdoc.go +1 -1
- package/linthost/rules_format_orphan_semi.go +1 -1
- package/linthost/rules_format_parameter_properties.go +17 -7
- package/linthost/rules_format_print_width.go +1 -1
- package/linthost/rules_format_quote_props.go +1 -1
- package/linthost/rules_format_quotes.go +1 -1
- package/linthost/rules_format_semi.go +106 -79
- package/linthost/rules_format_sort_imports.go +79 -45
- package/linthost/rules_format_statement_split.go +1 -1
- package/linthost/rules_format_ternary_nullish_parens.go +1 -1
- package/linthost/rules_format_trailing_comma.go +64 -1
- package/linthost/rules_format_whitespace.go +1 -1
- package/linthost/rules_functional.go +21 -22
- package/linthost/rules_gap.go +80 -3
- package/linthost/rules_grouped_accessor_pairs.go +84 -25
- package/linthost/rules_guard_for_in.go +64 -147
- package/linthost/rules_jsx_a11y.go +232 -22
- package/linthost/rules_logic.go +84 -38
- package/linthost/rules_nextjs.go +105 -16
- package/linthost/rules_no_class_assign.go +70 -0
- package/linthost/rules_no_duplicate_imports.go +266 -0
- package/linthost/rules_no_else_return.go +156 -38
- package/linthost/rules_no_extend_native.go +113 -17
- package/linthost/rules_no_fallthrough.go +1013 -0
- package/linthost/rules_no_func_assign.go +70 -0
- package/linthost/rules_no_import_assign.go +278 -86
- package/linthost/rules_no_inner_declarations.go +186 -0
- package/linthost/rules_no_loop_func.go +438 -53
- package/linthost/rules_no_mixed_operators.go +208 -52
- package/linthost/rules_no_param_reassign.go +376 -61
- package/linthost/rules_no_promise_executor_return.go +156 -0
- package/linthost/rules_no_restricted_imports.go +1013 -57
- package/linthost/rules_no_restricted_syntax.go +190 -26
- package/linthost/rules_no_useless_constructor.go +6 -36
- package/linthost/rules_problems.go +154 -311
- package/linthost/rules_promise.go +2578 -109
- package/linthost/rules_react_extras.go +71 -45
- package/linthost/rules_react_hooks.go +18 -2
- package/linthost/rules_react_perf.go +1 -0
- package/linthost/rules_react_refresh.go +1 -1
- package/linthost/rules_regexp.go +230 -14
- package/linthost/rules_security.go +45 -19
- package/linthost/rules_self.go +21 -1
- package/linthost/rules_solid.go +13 -7
- package/linthost/rules_storybook.go +1 -1
- package/linthost/rules_suggestions.go +490 -84
- package/linthost/rules_testing_library.go +12 -1
- package/linthost/rules_ts.go +227 -54
- package/linthost/rules_ts_async.go +0 -131
- package/linthost/rules_ts_no_base_to_string.go +3 -4
- package/linthost/rules_ts_no_misused_promises.go +817 -0
- package/linthost/rules_ts_no_restricted_types.go +320 -34
- package/linthost/rules_ts_no_unnecessary_qualifier.go +132 -55
- package/linthost/rules_ts_no_unsafe_assignment.go +608 -60
- package/linthost/rules_ts_require_array_sort_compare.go +5 -5
- package/linthost/rules_ts_switch_exhaustiveness_check.go +556 -86
- package/linthost/rules_unicorn_better_regex.go +224 -16
- package/linthost/rules_unicorn_consistent_destructuring.go +1002 -7
- package/linthost/rules_unicorn_consistent_existence_index_check.go +208 -29
- package/linthost/rules_unicorn_consistent_function_scoping.go +1041 -8
- package/linthost/rules_unicorn_consistent_template_literal_escape.go +178 -7
- package/linthost/rules_unicorn_escape_case.go +30 -19
- package/linthost/rules_unicorn_filename_case.go +763 -16
- package/linthost/rules_unicorn_import_style.go +783 -17
- package/linthost/rules_unicorn_isolated_functions.go +934 -14
- package/linthost/rules_unicorn_no_hex_escape.go +26 -11
- package/linthost/rules_unicorn_no_typeof_undefined.go +234 -25
- package/linthost/rules_unicorn_no_unnecessary_polyfills.go +376 -21
- package/linthost/rules_unicorn_no_unused_properties.go +756 -19
- package/linthost/rules_unicorn_number_literal_case.go +58 -39
- package/linthost/rules_unicorn_prefer_at.go +7 -3
- package/linthost/rules_unicorn_prefer_number_properties.go +381 -48
- package/linthost/rules_unicorn_prefer_optional_catch_binding.go +24 -24
- package/linthost/rules_unicorn_prefer_simple_condition_first.go +597 -19
- package/linthost/rules_unicorn_prefer_string_raw.go +147 -16
- package/linthost/rules_unicorn_prevent_abbreviations.go +1963 -95
- package/linthost/rules_unicorn_string_content.go +611 -15
- package/linthost/rules_unicorn_switch_case_break_position.go +172 -15
- package/linthost/rules_unicorn_template_indent.go +923 -18
- package/linthost/rules_unicorn_text_encoding_identifier_case.go +202 -42
- package/linthost/rules_unicorn_throw_new_error.go +218 -26
- package/linthost/rules_var.go +829 -142
- package/linthost/string_length.go +222 -0
- package/package.json +4 -3
- package/rule/project.go +145 -0
- package/rule/rule.go +58 -1
- package/src/index.ts +58 -17
- package/src/structures/ITtscLintConfig.ts +3 -0
- package/src/structures/TtscLintRuleSetting.ts +3 -2
- package/src/structures/format/ITtscLintFormat.ts +3 -2
- package/src/structures/format/ITtscLintFormatSortImports.ts +21 -4
- package/src/structures/rules/ITtscLintBoundariesRuleOptions.ts +122 -8
- package/src/structures/rules/ITtscLintBoundariesRules.ts +6 -8
- package/src/structures/rules/ITtscLintContributorRules.ts +18 -2
- package/src/structures/rules/ITtscLintCoreRuleOptions.ts +465 -0
- package/src/structures/rules/ITtscLintCoreRules.ts +122 -67
- package/src/structures/rules/ITtscLintRegexpRules.ts +7 -2
- package/src/structures/rules/ITtscLintRuleOptionsMap.ts +88 -7
- package/src/structures/rules/ITtscLintRules.ts +7 -2
- package/src/structures/rules/ITtscLintSolidRules.ts +0 -9
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +248 -0
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +64 -42
- package/src/structures/rules/ITtscLintUnicornRuleOptions.ts +350 -0
- package/src/structures/rules/ITtscLintUnicornRules.ts +60 -36
- package/src/structures/rules/index.ts +3 -0
package/linthost/rules_escape.go
CHANGED
|
@@ -2,6 +2,7 @@ package linthost
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"strings"
|
|
5
|
+
"unicode/utf8"
|
|
5
6
|
|
|
6
7
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
7
8
|
)
|
|
@@ -37,8 +38,11 @@ func (noUselessEscape) Check(ctx *Context, node *shimast.Node) {
|
|
|
37
38
|
// Tagged templates expose the raw bytes of the template to the tag
|
|
38
39
|
// function (`String.raw`, `dedent`, `gql`, `css`, …), so a backslash
|
|
39
40
|
// that looks redundant to the JS lexer is meaningful at the tag
|
|
40
|
-
// boundary. ESLint canonical skips tagged
|
|
41
|
-
|
|
41
|
+
// boundary. ESLint canonical skips a tagged template's own quasis, but a
|
|
42
|
+
// literal merely nested inside a tag's substitution is not itself tagged
|
|
43
|
+
// and stays checked — so this consults the literal's OWN enclosing
|
|
44
|
+
// template, not any ancestor tag.
|
|
45
|
+
if isTaggedTemplateElement(node) {
|
|
42
46
|
return
|
|
43
47
|
}
|
|
44
48
|
// tsgo's `node.Pos()` points at the start of leading trivia; the regex
|
|
@@ -82,6 +86,16 @@ const templateValidEscapes = "`'\"\\bfnrtv0xuU$\n\r"
|
|
|
82
86
|
const regexNonClassValidEscapes = "^$\\.*+?()[]{}|/-\n\r"
|
|
83
87
|
const regexClassValidEscapes = "\\]-^\n\r"
|
|
84
88
|
|
|
89
|
+
// regexClassSetValidEscapes is the in-class allowlist for a `v`-flag
|
|
90
|
+
// (unicodeSets) regex. There the ClassSetSyntaxCharacters `( ) [ ] { } / | -`
|
|
91
|
+
// stay meaningful inside `[...]`, so a backslash before them is load-bearing:
|
|
92
|
+
// stripping it (e.g. `/[\(]/v` → `/[(]/v`) turns valid source into a
|
|
93
|
+
// `SyntaxError: Invalid character in character class`. Mirrors ESLint's
|
|
94
|
+
// REGEX_CLASSSET_CHARACTER_ESCAPES; its `q` (from `\q{…}`) is handled with the
|
|
95
|
+
// other shorthand letters in `isUselessRegexEscape`. `]` and `-` already sit
|
|
96
|
+
// in the base set above.
|
|
97
|
+
const regexClassSetValidEscapes = regexClassValidEscapes + "()[{}|/"
|
|
98
|
+
|
|
85
99
|
// reportStringEscapes walks the raw source bytes of a string or template
|
|
86
100
|
// literal and reports each backslash whose following character is not in
|
|
87
101
|
// `whitelist`. `base` is the source offset of `raw[0]` so reported ranges
|
|
@@ -128,21 +142,18 @@ func reportStringEscapes(ctx *Context, raw string, base int, whitelist string, i
|
|
|
128
142
|
continue
|
|
129
143
|
}
|
|
130
144
|
if isUselessStringEscape(next, whitelist) {
|
|
145
|
+
message := uselessEscapeMessage(raw[i+1:])
|
|
131
146
|
// Only emit a fix when both surrounding bytes are plain ASCII so
|
|
132
147
|
// deleting one byte cannot corrupt a multi-byte sequence.
|
|
133
148
|
if next < 0x80 {
|
|
134
149
|
ctx.ReportRangeFix(
|
|
135
150
|
base+i,
|
|
136
151
|
base+i+1,
|
|
137
|
-
|
|
152
|
+
message,
|
|
138
153
|
TextEdit{Pos: base + i, End: base + i + 1, Text: ""},
|
|
139
154
|
)
|
|
140
155
|
} else {
|
|
141
|
-
ctx.ReportRange(
|
|
142
|
-
base+i,
|
|
143
|
-
base+i+1,
|
|
144
|
-
"Unnecessary escape character: \\"+string(next)+".",
|
|
145
|
-
)
|
|
156
|
+
ctx.ReportRange(base+i, base+i+1, message)
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
i++ // skip the escaped char so `\\\\` doesn't double-report.
|
|
@@ -151,7 +162,9 @@ func reportStringEscapes(ctx *Context, raw string, base int, whitelist string, i
|
|
|
151
162
|
|
|
152
163
|
// reportRegexEscapes walks the pattern body of a regex literal and reports
|
|
153
164
|
// backslashes that escape non-special characters. `base` is the source offset
|
|
154
|
-
// of `raw[0]`. Character-class context (`[…]`) widens the legal set slightly
|
|
165
|
+
// of `raw[0]`. Character-class context (`[…]`) widens the legal set slightly,
|
|
166
|
+
// and the trailing `v` (unicodeSets) flag widens it further because more
|
|
167
|
+
// punctuation is meaningful inside a class in that mode.
|
|
155
168
|
func reportRegexEscapes(ctx *Context, raw string, base int) {
|
|
156
169
|
if len(raw) < 3 || raw[0] != '/' {
|
|
157
170
|
return
|
|
@@ -163,6 +176,12 @@ func reportRegexEscapes(ctx *Context, raw string, base int) {
|
|
|
163
176
|
return
|
|
164
177
|
}
|
|
165
178
|
body := raw[1:closing]
|
|
179
|
+
// Flags follow the closing slash. The `v` (unicodeSets) flag promotes the
|
|
180
|
+
// ClassSetSyntaxCharacters to meaningful members of a character class, so
|
|
181
|
+
// their escapes must be preserved there; deleting one corrupts the source.
|
|
182
|
+
// Mirrors ESLint selecting REGEX_CLASSSET_CHARACTER_ESCAPES over
|
|
183
|
+
// REGEX_GENERAL_ESCAPES on that flag.
|
|
184
|
+
unicodeSets := strings.IndexByte(raw[closing+1:], 'v') >= 0
|
|
166
185
|
inClass := false
|
|
167
186
|
for i := 0; i < len(body); i++ {
|
|
168
187
|
ch := body[i]
|
|
@@ -178,29 +197,39 @@ func reportRegexEscapes(ctx *Context, raw string, base int) {
|
|
|
178
197
|
continue
|
|
179
198
|
}
|
|
180
199
|
next := body[i+1]
|
|
181
|
-
if isUselessRegexEscape(next, inClass) {
|
|
200
|
+
if isUselessRegexEscape(next, inClass, unicodeSets) {
|
|
182
201
|
// base + 1 (for the leading `/`) + i is the byte offset of the
|
|
183
202
|
// backslash in the source.
|
|
184
203
|
pos := base + 1 + i
|
|
204
|
+
message := uselessEscapeMessage(body[i+1:])
|
|
185
205
|
if next < 0x80 {
|
|
186
206
|
ctx.ReportRangeFix(
|
|
187
207
|
pos,
|
|
188
208
|
pos+1,
|
|
189
|
-
|
|
209
|
+
message,
|
|
190
210
|
TextEdit{Pos: pos, End: pos + 1, Text: ""},
|
|
191
211
|
)
|
|
192
212
|
} else {
|
|
193
|
-
ctx.ReportRange(
|
|
194
|
-
pos,
|
|
195
|
-
pos+1,
|
|
196
|
-
"Unnecessary escape character: \\"+string(next)+".",
|
|
197
|
-
)
|
|
213
|
+
ctx.ReportRange(pos, pos+1, message)
|
|
198
214
|
}
|
|
199
215
|
}
|
|
200
216
|
i++ // consume the escaped character.
|
|
201
217
|
}
|
|
202
218
|
}
|
|
203
219
|
|
|
220
|
+
// uselessEscapeMessage renders the diagnostic for one redundant backslash.
|
|
221
|
+
// `rest` is the raw source starting at the escaped character, of which only
|
|
222
|
+
// the leading UTF-8 rune is named. Decoding is required because the escape
|
|
223
|
+
// target may be multi-byte: converting the lone lead byte would reinterpret it
|
|
224
|
+
// as the code point of the same numeric value, so `\你` (lead byte 0xE4) would
|
|
225
|
+
// accuse `ä` (U+00E4). ESLint canonical names the whole code point as well.
|
|
226
|
+
// A byte that is not valid UTF-8 decodes to U+FFFD, which keeps the message
|
|
227
|
+
// itself well-formed.
|
|
228
|
+
func uselessEscapeMessage(rest string) string {
|
|
229
|
+
escaped, _ := utf8.DecodeRuneInString(rest)
|
|
230
|
+
return "Unnecessary escape character: \\" + string(escaped) + "."
|
|
231
|
+
}
|
|
232
|
+
|
|
204
233
|
// isUselessStringEscape reports whether a backslash before `ch` is redundant
|
|
205
234
|
// inside a string or template literal. The `whitelist` contains the characters
|
|
206
235
|
// that are valid escape targets for the specific literal kind (string vs template).
|
|
@@ -212,10 +241,16 @@ func isUselessStringEscape(ch byte, whitelist string) bool {
|
|
|
212
241
|
if strings.IndexByte(whitelist, ch) >= 0 {
|
|
213
242
|
return false
|
|
214
243
|
}
|
|
244
|
+
// Digits 1-9 are octal-shaped (`no-octal-escape` owns those; `0` is in
|
|
245
|
+
// the whitelist). Deleting the backslash of `\1`…`\7` changes the cooked
|
|
246
|
+
// string value, and upstream ESLint skips backslash-digit entirely, so
|
|
247
|
+
// `\8`/`\9` are exempt too for oracle parity.
|
|
248
|
+
if ch >= '1' && ch <= '9' {
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
215
251
|
// ASCII letters that aren't in the whitelist are user-error escapes
|
|
216
|
-
// like `\a`, `\m`.
|
|
217
|
-
//
|
|
218
|
-
// whitelist (e.g., `\.`) is also redundant.
|
|
252
|
+
// like `\a`, `\m`. Punctuation that isn't in the whitelist (e.g., `\.`)
|
|
253
|
+
// is also redundant.
|
|
219
254
|
return true
|
|
220
255
|
}
|
|
221
256
|
|
|
@@ -224,12 +259,18 @@ func isUselessStringEscape(ch byte, whitelist string) bool {
|
|
|
224
259
|
// character class, which narrows the set of meaningful meta-char escapes:
|
|
225
260
|
// most regex meta-chars (`.`, `*`, `+`, `?`, `(`, `)`, `{`, `}`, `|`, `^`,
|
|
226
261
|
// `$`, `/`) are literal inside a class, so escaping them there is noise.
|
|
227
|
-
|
|
262
|
+
// `unicodeSets` is true for a `v`-flag regex, where the ClassSetSyntaxCharacters
|
|
263
|
+
// regain meaning inside a class and their escapes are therefore required.
|
|
264
|
+
func isUselessRegexEscape(ch byte, inClass, unicodeSets bool) bool {
|
|
228
265
|
if ch < 0x20 {
|
|
229
266
|
return false
|
|
230
267
|
}
|
|
231
268
|
if inClass {
|
|
232
|
-
|
|
269
|
+
classEscapes := regexClassValidEscapes
|
|
270
|
+
if unicodeSets {
|
|
271
|
+
classEscapes = regexClassSetValidEscapes
|
|
272
|
+
}
|
|
273
|
+
if strings.IndexByte(classEscapes, ch) >= 0 {
|
|
233
274
|
return false
|
|
234
275
|
}
|
|
235
276
|
} else if strings.IndexByte(regexNonClassValidEscapes, ch) >= 0 {
|
|
@@ -258,33 +299,6 @@ func isUselessRegexEscape(ch byte, inClass bool) bool {
|
|
|
258
299
|
return true
|
|
259
300
|
}
|
|
260
301
|
|
|
261
|
-
// isInsideTaggedTemplate reports whether `node` is the template payload of
|
|
262
|
-
// a TaggedTemplateExpression. Two AST shapes reach here:
|
|
263
|
-
//
|
|
264
|
-
// - `KindNoSubstitutionTemplateLiteral` — the direct child of a
|
|
265
|
-
// TaggedTemplateExpression's Template slot.
|
|
266
|
-
// - `KindTemplateHead/Middle/Tail` — wrapped in TemplateSpan and
|
|
267
|
-
// TemplateExpression nodes; the TemplateExpression's parent is the
|
|
268
|
-
// TaggedTemplateExpression.
|
|
269
|
-
func isInsideTaggedTemplate(node *shimast.Node) bool {
|
|
270
|
-
if node == nil || node.Parent == nil {
|
|
271
|
-
return false
|
|
272
|
-
}
|
|
273
|
-
parent := node.Parent
|
|
274
|
-
if parent.Kind == shimast.KindTaggedTemplateExpression {
|
|
275
|
-
return true
|
|
276
|
-
}
|
|
277
|
-
// Walk up at most two more hops to reach the TaggedTemplateExpression
|
|
278
|
-
// for the spans family.
|
|
279
|
-
for i := 0; i < 2 && parent.Parent != nil; i++ {
|
|
280
|
-
parent = parent.Parent
|
|
281
|
-
if parent.Kind == shimast.KindTaggedTemplateExpression {
|
|
282
|
-
return true
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return false
|
|
286
|
-
}
|
|
287
|
-
|
|
288
302
|
func init() {
|
|
289
303
|
Register(noUselessEscape{})
|
|
290
304
|
}
|
|
@@ -17,13 +17,16 @@ import (
|
|
|
17
17
|
// other shape (`(x: T)`, `({ x })`, `(...x)`, `(x = 1)`, `(x?)`, `(x, y)`,
|
|
18
18
|
// `()`) keeps its parentheses in both modes, exactly as Prettier does.
|
|
19
19
|
// `async x => x` is handled too: the modifier sits before the parameter, so
|
|
20
|
-
// the parameter-name span is unaffected.
|
|
20
|
+
// the parameter-name span is unaffected. A legal trailing comma (`(x,) => x`)
|
|
21
|
+
// counts as already parenthesized — a trailing comma can only exist inside a
|
|
22
|
+
// parameter-list paren pair — so "always" leaves it alone and "avoid" deletes
|
|
23
|
+
// the comma together with the parens (`x => x`, matching Prettier).
|
|
21
24
|
//
|
|
22
25
|
// The rule rewrites only the parameter span (`x` <-> `(x)`), never the body
|
|
23
26
|
// or the `=>`, so a chained arrow `a => b => …` has each eligible arm
|
|
24
27
|
// normalized independently. Idempotent: a parameter already in the
|
|
25
28
|
// preferred shape compares equal and emits nothing.
|
|
26
|
-
type formatArrowParens struct{}
|
|
29
|
+
type formatArrowParens struct{ optionsRule }
|
|
27
30
|
|
|
28
31
|
type formatArrowParensOptions struct {
|
|
29
32
|
Prefer string `json:"prefer"`
|
|
@@ -90,9 +93,13 @@ func (formatArrowParens) Check(ctx *Context, node *shimast.Node) {
|
|
|
90
93
|
|
|
91
94
|
// Is the parameter already wrapped in `(` … `)`? Scan over whitespace on
|
|
92
95
|
// each side; the sole parameter of an arrow is delimited by the
|
|
93
|
-
// parameter-list parens when present.
|
|
96
|
+
// parameter-list parens when present. The forward scan starts at the
|
|
97
|
+
// parameter *list*'s end, not the name's: the list span covers a legal
|
|
98
|
+
// trailing comma (`(x,)` — that is how the AST records HasTrailingComma),
|
|
99
|
+
// so a trailing-comma list is classified as wrapped instead of the `,`
|
|
100
|
+
// byte aborting the scan and mis-reporting a bare name.
|
|
94
101
|
openParen := scanBackForByte(src, nameStart, '(')
|
|
95
|
-
closeParen := scanForwardForByte(src,
|
|
102
|
+
closeParen := scanForwardForByte(src, arrow.Parameters.End(), ')')
|
|
96
103
|
wrapped := openParen >= 0 && closeParen >= 0
|
|
97
104
|
|
|
98
105
|
switch prefer {
|
|
@@ -189,7 +196,10 @@ func arrowParamRegionHasComment(src string, paramPos, nameStart, nameEnd int) bo
|
|
|
189
196
|
// paren so a comment between `)` and `=>` (`(x) /* c */ => x`) — which in
|
|
190
197
|
// "avoid" mode would otherwise be stranded when the parens are stripped — is
|
|
191
198
|
// also detected (Prettier keeps the parens of an arrow with such a dangling
|
|
192
|
-
// comment).
|
|
199
|
+
// comment). Likewise tolerate one trailing comma before the paren so a
|
|
200
|
+
// comment on either side of it (`(x /* c */,) => x`, `(x, /* c */) => x`) —
|
|
201
|
+
// which the "avoid" deletion would destroy — is also detected.
|
|
202
|
+
sawComma := false
|
|
193
203
|
sawParen := false
|
|
194
204
|
for i := nameEnd; i+1 < len(src); i++ {
|
|
195
205
|
c := src[i]
|
|
@@ -199,6 +209,10 @@ func arrowParamRegionHasComment(src string, paramPos, nameStart, nameEnd int) bo
|
|
|
199
209
|
if c == '/' && (src[i+1] == '*' || src[i+1] == '/') {
|
|
200
210
|
return true
|
|
201
211
|
}
|
|
212
|
+
if c == ',' && !sawComma && !sawParen {
|
|
213
|
+
sawComma = true
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
202
216
|
if c == ')' && !sawParen {
|
|
203
217
|
sawParen = true
|
|
204
218
|
continue
|
|
@@ -22,7 +22,7 @@ import (
|
|
|
22
22
|
// and an empty `{}` has no interior to pad. It rewrites just the whitespace
|
|
23
23
|
// run immediately inside each brace, so it never disturbs the contents.
|
|
24
24
|
// Idempotent: a pair already in the preferred shape compares equal.
|
|
25
|
-
type formatBracketSpacing struct{}
|
|
25
|
+
type formatBracketSpacing struct{ optionsRule }
|
|
26
26
|
|
|
27
27
|
type formatBracketSpacingOptions struct {
|
|
28
28
|
Spacing *bool `json:"spacing"`
|
|
@@ -23,7 +23,7 @@ import (
|
|
|
23
23
|
// `format/indent` (leading whitespace of a line) or `format/print-width`
|
|
24
24
|
// (interior reflow). Idempotent: once joined the gap holds no newline
|
|
25
25
|
// and the rule abstains.
|
|
26
|
-
type formatClauseJoin struct{}
|
|
26
|
+
type formatClauseJoin struct{ optionsRule }
|
|
27
27
|
|
|
28
28
|
// formatClauseJoinOptions mirrors the printWidth/indent keys the rule
|
|
29
29
|
// needs to decide whether the joined line fits. The config layer mirrors
|
|
@@ -46,7 +46,7 @@ import (
|
|
|
46
46
|
// breaking heritage clause at once), a header carrying comments, or a
|
|
47
47
|
// type/parameter that is itself multi-line makes the rule ABSTAIN, so it
|
|
48
48
|
// never emits a header it cannot reproduce exactly.
|
|
49
|
-
type formatDeclarationHeader struct{}
|
|
49
|
+
type formatDeclarationHeader struct{ optionsRule }
|
|
50
50
|
|
|
51
51
|
type formatDeclarationHeaderOptions struct {
|
|
52
52
|
PrintWidth *int `json:"printWidth"`
|
|
@@ -172,7 +172,7 @@ func (formatDeclarationHeader) Check(ctx *Context, node *shimast.Node) {
|
|
|
172
172
|
// with a non-empty body; an interface (any body) and an empty body
|
|
173
173
|
// keep `{` glued to the last header line (so an empty body reads
|
|
174
174
|
// `… {}`). isClass && !emptyBody captures that.
|
|
175
|
-
brace := headerBrace(isClass, emptyBody, base)
|
|
175
|
+
brace := headerBrace(isClass, emptyBody, base, layout.eol)
|
|
176
176
|
target, ok = brokenDeclarationHeader(src, base, prefix, typeParams, paramTexts, clauses, layout, brace, emptyBody, isClass)
|
|
177
177
|
if !ok {
|
|
178
178
|
return // unverified combination: abstain
|
|
@@ -194,6 +194,11 @@ type declarationHeaderLayout struct {
|
|
|
194
194
|
printWidth int
|
|
195
195
|
tabWidth int
|
|
196
196
|
oneLevel string
|
|
197
|
+
// eol is the newline the reflow builders synthesize. It mirrors
|
|
198
|
+
// loadFormatLayout: `"\n"` by default, `"\r\n"` under endOfLine:"crlf",
|
|
199
|
+
// so a reflowed CRLF header stays consistently CRLF instead of gaining
|
|
200
|
+
// lone LFs.
|
|
201
|
+
eol string
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
func (l declarationHeaderLayout) indent(base string, depth int) string {
|
|
@@ -203,7 +208,7 @@ func (l declarationHeaderLayout) indent(base string, depth int) string {
|
|
|
203
208
|
func loadDeclarationHeaderLayout(ctx *Context) declarationHeaderLayout {
|
|
204
209
|
var opts formatDeclarationHeaderOptions
|
|
205
210
|
_ = ctx.DecodeOptions(&opts)
|
|
206
|
-
l := declarationHeaderLayout{printWidth: 80, tabWidth: 2, oneLevel: " "}
|
|
211
|
+
l := declarationHeaderLayout{printWidth: 80, tabWidth: 2, oneLevel: " ", eol: "\n"}
|
|
207
212
|
if opts.PrintWidth != nil && *opts.PrintWidth > 0 {
|
|
208
213
|
l.printWidth = *opts.PrintWidth
|
|
209
214
|
}
|
|
@@ -215,6 +220,9 @@ func loadDeclarationHeaderLayout(ctx *Context) declarationHeaderLayout {
|
|
|
215
220
|
} else if opts.TabWidth != nil && *opts.TabWidth > 0 {
|
|
216
221
|
l.oneLevel = strings.Repeat(" ", *opts.TabWidth)
|
|
217
222
|
}
|
|
223
|
+
if opts.EndOfLine != nil && *opts.EndOfLine == "crlf" {
|
|
224
|
+
l.eol = "\r\n"
|
|
225
|
+
}
|
|
218
226
|
return l
|
|
219
227
|
}
|
|
220
228
|
|
|
@@ -392,11 +400,11 @@ func multiClauseHeader(prefix string, clauses []heritageClauseText, layout decla
|
|
|
392
400
|
indent1 := layout.indent(base, 1)
|
|
393
401
|
indent2 := layout.indent(base, 2)
|
|
394
402
|
for ci, c := range clauses {
|
|
395
|
-
b.WriteString(
|
|
403
|
+
b.WriteString(layout.eol)
|
|
396
404
|
b.WriteString(indent1)
|
|
397
405
|
b.WriteString(c.keyword)
|
|
398
406
|
inlineWidth := visualWidth(indent1+c.keyword+" "+strings.Join(c.types, ", "), layout.tabWidth)
|
|
399
|
-
if ci == len(clauses)-1 && !strings.HasPrefix(brace,
|
|
407
|
+
if ci == len(clauses)-1 && !strings.HasPrefix(brace, layout.eol) {
|
|
400
408
|
inlineWidth += visualWidth(brace, layout.tabWidth)
|
|
401
409
|
}
|
|
402
410
|
if inlineWidth <= layout.printWidth {
|
|
@@ -405,7 +413,7 @@ func multiClauseHeader(prefix string, clauses []heritageClauseText, layout decla
|
|
|
405
413
|
continue
|
|
406
414
|
}
|
|
407
415
|
for i, t := range c.types {
|
|
408
|
-
b.WriteString(
|
|
416
|
+
b.WriteString(layout.eol)
|
|
409
417
|
b.WriteString(indent2)
|
|
410
418
|
b.WriteString(t)
|
|
411
419
|
if i < len(c.types)-1 {
|
|
@@ -427,7 +435,7 @@ func multiTypeHeader(prefix string, clause heritageClauseText, layout declaratio
|
|
|
427
435
|
indent1 := layout.indent(base, 1)
|
|
428
436
|
inline := indent1 + clause.keyword + " " + strings.Join(clause.types, ", ")
|
|
429
437
|
inlineWidth := visualWidth(inline, layout.tabWidth)
|
|
430
|
-
if !strings.HasPrefix(brace,
|
|
438
|
+
if !strings.HasPrefix(brace, layout.eol) {
|
|
431
439
|
inlineWidth += visualWidth(brace, layout.tabWidth)
|
|
432
440
|
}
|
|
433
441
|
if emptyBody {
|
|
@@ -437,16 +445,16 @@ func multiTypeHeader(prefix string, clause heritageClauseText, layout declaratio
|
|
|
437
445
|
inlineWidth++
|
|
438
446
|
}
|
|
439
447
|
if inlineWidth <= layout.printWidth {
|
|
440
|
-
return prefix +
|
|
448
|
+
return prefix + layout.eol + inline + brace
|
|
441
449
|
}
|
|
442
450
|
|
|
443
451
|
var b strings.Builder
|
|
444
452
|
b.WriteString(prefix)
|
|
445
|
-
b.WriteString(
|
|
453
|
+
b.WriteString(layout.eol)
|
|
446
454
|
b.WriteString(indent1)
|
|
447
455
|
b.WriteString(clause.keyword)
|
|
448
456
|
for i, t := range clause.types {
|
|
449
|
-
b.WriteString(
|
|
457
|
+
b.WriteString(layout.eol)
|
|
450
458
|
b.WriteString(layout.indent(base, 2))
|
|
451
459
|
b.WriteString(t)
|
|
452
460
|
if i < len(clause.types)-1 {
|
|
@@ -524,14 +532,15 @@ func singleGenericHeritageHeader(src, base, prefix string, typeParams, heritage
|
|
|
524
532
|
b.WriteString(keyword)
|
|
525
533
|
b.WriteString(" ")
|
|
526
534
|
b.WriteString(typeName)
|
|
527
|
-
b.WriteString("
|
|
535
|
+
b.WriteString("<")
|
|
536
|
+
b.WriteString(layout.eol)
|
|
528
537
|
for i, a := range args {
|
|
529
538
|
b.WriteString(layout.indent(base, 1))
|
|
530
539
|
b.WriteString(a)
|
|
531
540
|
if i < len(args)-1 {
|
|
532
541
|
b.WriteString(",")
|
|
533
542
|
}
|
|
534
|
-
b.WriteString(
|
|
543
|
+
b.WriteString(layout.eol)
|
|
535
544
|
}
|
|
536
545
|
b.WriteString(base)
|
|
537
546
|
b.WriteString("> {")
|
|
@@ -541,10 +550,11 @@ func singleGenericHeritageHeader(src, base, prefix string, typeParams, heritage
|
|
|
541
550
|
// headerBrace returns the opening-brace fragment that closes a broken
|
|
542
551
|
// header. Prettier puts `{` on its own line only for a class with a
|
|
543
552
|
// non-empty body; an interface and any empty body keep it glued to the
|
|
544
|
-
// last header line.
|
|
545
|
-
|
|
553
|
+
// last header line. `eol` is the synthesized newline (LF or CRLF) so the
|
|
554
|
+
// own-line brace matches the file's line endings.
|
|
555
|
+
func headerBrace(isClass, emptyBody bool, base, eol string) string {
|
|
546
556
|
if isClass && !emptyBody {
|
|
547
|
-
return
|
|
557
|
+
return eol + base + "{"
|
|
548
558
|
}
|
|
549
559
|
return " {"
|
|
550
560
|
}
|
|
@@ -579,11 +589,13 @@ func typeParamExplodeHeader(
|
|
|
579
589
|
) string {
|
|
580
590
|
var b strings.Builder
|
|
581
591
|
b.WriteString(prefix)
|
|
582
|
-
b.WriteString("
|
|
592
|
+
b.WriteString("<")
|
|
593
|
+
b.WriteString(layout.eol)
|
|
583
594
|
paramIndent := layout.indent(base, 1)
|
|
584
595
|
for _, p := range typeParams.Nodes {
|
|
585
596
|
b.WriteString(renderExplodedTypeParam(src, p, layout, paramIndent))
|
|
586
|
-
b.WriteString("
|
|
597
|
+
b.WriteString(",")
|
|
598
|
+
b.WriteString(layout.eol)
|
|
587
599
|
}
|
|
588
600
|
b.WriteString(base)
|
|
589
601
|
b.WriteString(">")
|
|
@@ -594,7 +606,7 @@ func typeParamExplodeHeader(
|
|
|
594
606
|
// >
|
|
595
607
|
// extends IPage.IRequest {
|
|
596
608
|
c := clauses[0]
|
|
597
|
-
b.WriteString(
|
|
609
|
+
b.WriteString(layout.eol)
|
|
598
610
|
b.WriteString(layout.indent(base, 1))
|
|
599
611
|
b.WriteString(c.keyword)
|
|
600
612
|
b.WriteString(" ")
|
|
@@ -628,7 +640,7 @@ func renderExplodedTypeParam(src string, p *shimast.Node, layout declarationHead
|
|
|
628
640
|
head := strings.TrimRight(src[start:defStart], " \t") // ends in `=`
|
|
629
641
|
def := strings.TrimSpace(src[defStart:end])
|
|
630
642
|
if head != "" && def != "" {
|
|
631
|
-
return paramIndent + head +
|
|
643
|
+
return paramIndent + head + layout.eol + paramIndent + layout.oneLevel + def
|
|
632
644
|
}
|
|
633
645
|
}
|
|
634
646
|
}
|
|
@@ -31,7 +31,7 @@ import (
|
|
|
31
31
|
//
|
|
32
32
|
// Idempotent: a correctly-indented statement compares equal in step 3
|
|
33
33
|
// and emits nothing.
|
|
34
|
-
type formatIndent struct{}
|
|
34
|
+
type formatIndent struct{ optionsRule }
|
|
35
35
|
|
|
36
36
|
func (formatIndent) Name() string { return "format/indent" }
|
|
37
37
|
func (formatIndent) IsFormat() bool { return true }
|
|
@@ -25,7 +25,7 @@ import (
|
|
|
25
25
|
// `/** ... */` blocks; it deliberately avoids relying on the JSDoc AST
|
|
26
26
|
// because comment attachment is a moving target across TypeScript
|
|
27
27
|
// versions.
|
|
28
|
-
type formatJSDoc struct{}
|
|
28
|
+
type formatJSDoc struct{ optionsRule }
|
|
29
29
|
|
|
30
30
|
// formatJSDocOptions mirrors `TtscLintRuleOptions.JSDoc`. The
|
|
31
31
|
// `tagSynonyms` map layers on top of the built-in synonym table so
|
|
@@ -24,7 +24,7 @@ import (
|
|
|
24
24
|
// is left alone, dropping a redundant `;` safely depends on the
|
|
25
25
|
// surrounding semicolon policy and is out of scope here. Idempotent:
|
|
26
26
|
// once merged the gap holds no newline, so the rule finds nothing to do.
|
|
27
|
-
type formatOrphanSemi struct{}
|
|
27
|
+
type formatOrphanSemi struct{ optionsRule }
|
|
28
28
|
|
|
29
29
|
// formatOrphanSemiOptions carries the effective `semi` setting, mirrored
|
|
30
30
|
// from format.semi by the config layer. Defaults to true (semicolons),
|
|
@@ -25,13 +25,15 @@ import (
|
|
|
25
25
|
// region and emits no trailing comma, format/trailing-comma adds it on
|
|
26
26
|
// the now-multi-line list, so the two rules stay disjoint. Idempotent:
|
|
27
27
|
// an already-broken list contains a newline and is skipped.
|
|
28
|
-
type formatParameterProperties struct{}
|
|
28
|
+
type formatParameterProperties struct{ optionsRule }
|
|
29
29
|
|
|
30
|
-
// formatParameterPropertiesOptions carries the indentation settings
|
|
31
|
-
// rewrite needs. The config layer mirrors
|
|
30
|
+
// formatParameterPropertiesOptions carries the indentation + EOL settings
|
|
31
|
+
// the rewrite needs. The config layer mirrors
|
|
32
|
+
// format.tabWidth/useTabs/endOfLine in.
|
|
32
33
|
type formatParameterPropertiesOptions struct {
|
|
33
|
-
TabWidth
|
|
34
|
-
UseTabs
|
|
34
|
+
TabWidth *int `json:"tabWidth"`
|
|
35
|
+
UseTabs *bool `json:"useTabs"`
|
|
36
|
+
EndOfLine *string `json:"endOfLine"`
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
func (formatParameterProperties) Name() string { return "format/parameter-properties" }
|
|
@@ -111,10 +113,18 @@ func (formatParameterProperties) Check(ctx *Context, node *shimast.Node) {
|
|
|
111
113
|
} else if opts.TabWidth != nil && *opts.TabWidth > 0 {
|
|
112
114
|
oneLevel = strings.Repeat(" ", *opts.TabWidth)
|
|
113
115
|
}
|
|
116
|
+
// The synthesized break mirrors loadFormatLayout: `"\n"` by default,
|
|
117
|
+
// `"\r\n"` under endOfLine:"crlf", so a broken CRLF constructor keeps
|
|
118
|
+
// consistent line endings instead of gaining lone LFs.
|
|
119
|
+
eol := "\n"
|
|
120
|
+
if opts.EndOfLine != nil && *opts.EndOfLine == "crlf" {
|
|
121
|
+
eol = "\r\n"
|
|
122
|
+
}
|
|
114
123
|
inner := indent + oneLevel
|
|
115
124
|
|
|
116
125
|
var b strings.Builder
|
|
117
|
-
b.WriteString("(
|
|
126
|
+
b.WriteString("(")
|
|
127
|
+
b.WriteString(eol)
|
|
118
128
|
for i, p := range params {
|
|
119
129
|
if p == nil {
|
|
120
130
|
return
|
|
@@ -129,7 +139,7 @@ func (formatParameterProperties) Check(ctx *Context, node *shimast.Node) {
|
|
|
129
139
|
if i < len(params)-1 {
|
|
130
140
|
b.WriteByte(',')
|
|
131
141
|
}
|
|
132
|
-
b.
|
|
142
|
+
b.WriteString(eol)
|
|
133
143
|
}
|
|
134
144
|
b.WriteString(indent)
|
|
135
145
|
b.WriteByte(')')
|
|
@@ -49,7 +49,7 @@ import (
|
|
|
49
49
|
// configured severities. Reuses the `error` severity caveat from
|
|
50
50
|
// other format rules: only set `error` once the full reflow coverage
|
|
51
51
|
// is mature.
|
|
52
|
-
type formatPrintWidth struct{}
|
|
52
|
+
type formatPrintWidth struct{ optionsRule }
|
|
53
53
|
|
|
54
54
|
// formatPrintWidthOptions mirrors `TtscLintRuleOptions.PrintWidth`.
|
|
55
55
|
//
|
|
@@ -20,7 +20,7 @@ import (
|
|
|
20
20
|
// the first) with no escapes. A key with escapes, unicode, a leading digit
|
|
21
21
|
// (numeric-looking), or any non-identifier byte is left quoted in every
|
|
22
22
|
// mode, so the rule can never produce an invalid key. Idempotent.
|
|
23
|
-
type formatQuoteProps struct{}
|
|
23
|
+
type formatQuoteProps struct{ optionsRule }
|
|
24
24
|
|
|
25
25
|
type formatQuotePropsOptions struct {
|
|
26
26
|
Mode string `json:"mode"`
|
|
@@ -23,7 +23,7 @@ import (
|
|
|
23
23
|
// quotes corrupts working code. Template literals, no-substitution
|
|
24
24
|
// template literals, and JSX text nodes use distinct AST kinds and are
|
|
25
25
|
// also intentionally out of scope.
|
|
26
|
-
type formatQuotes struct{}
|
|
26
|
+
type formatQuotes struct{ optionsRule }
|
|
27
27
|
|
|
28
28
|
// formatQuotesOptions mirrors `TtscLintRuleOptions.Quotes`. Prefer accepts
|
|
29
29
|
// `"double"` (the default, enforces double quotes) or `"single"` (enforces
|