@ttsc/lint 0.18.4 → 0.19.1
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 +125 -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 +159 -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 +231 -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 +206 -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
|
@@ -15,7 +15,7 @@ import (
|
|
|
15
15
|
// optional semicolon. Body-shaped declarations (functions, classes,
|
|
16
16
|
// namespaces, enums) and control-flow statements (if/for/while/try)
|
|
17
17
|
// are out of scope because they parse correctly without a terminator.
|
|
18
|
-
type formatSemi struct{}
|
|
18
|
+
type formatSemi struct{ optionsRule }
|
|
19
19
|
|
|
20
20
|
// formatSemiOptions is the Go mirror of `TtscLintRuleOptions.Semi`. The
|
|
21
21
|
// JSON tag matches the TypeScript field name so users get the same key
|
|
@@ -135,12 +135,22 @@ func (formatSemi) Check(ctx *Context, node *shimast.Node) {
|
|
|
135
135
|
)
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
// nextStatementHasASIHazard reports whether the
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
138
|
+
// nextStatementHasASIHazard reports whether removing the trailing `;`
|
|
139
|
+
// at `end-1` could change the parse, judged by the next significant
|
|
140
|
+
// byte after `end` and the line structure between them.
|
|
141
|
+
//
|
|
142
|
+
// ASI only inserts a semicolon at a line terminator, end of input, or
|
|
143
|
+
// before `}`. So the `;` is removable in exactly two shapes:
|
|
144
|
+
//
|
|
145
|
+
// - end of input or a same-line `}` follows (ASI's closing-brace and
|
|
146
|
+
// end-of-input rules apply), or
|
|
147
|
+
// - a line terminator separates the statement from the next token AND
|
|
148
|
+
// that token is not a continuation hazard.
|
|
149
|
+
//
|
|
150
|
+
// Any other same-line successor (`else`, `while` of a do-loop, another
|
|
151
|
+
// statement after a gap comment) makes the `;` a REQUIRED separator:
|
|
152
|
+
// no line terminator means ASI cannot fire, so stripping would be a
|
|
153
|
+
// syntax error, not a style change.
|
|
144
154
|
//
|
|
145
155
|
// Hazard tokens per the ASI spec:
|
|
146
156
|
//
|
|
@@ -149,18 +159,72 @@ func (formatSemi) Check(ctx *Context, node *shimast.Node) {
|
|
|
149
159
|
// - “ ` “: tagged template literal continues
|
|
150
160
|
// - `+`, `-`, `*`: binary operator continues
|
|
151
161
|
// - `,`: comma operator continues
|
|
152
|
-
// - `/`: division operator or regex literal continues
|
|
153
|
-
//
|
|
154
|
-
// hazard
|
|
162
|
+
// - `/`: division operator or regex literal continues (a leading `//`
|
|
163
|
+
// or `/*` is trivia consumed by scanPastTrivia; a bare `/` is a
|
|
164
|
+
// hazard).
|
|
155
165
|
func nextStatementHasASIHazard(src string, end int) bool {
|
|
156
|
-
|
|
166
|
+
i, sawNewline := scanPastTrivia(src, end)
|
|
167
|
+
if i >= len(src) {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
c := src[i]
|
|
171
|
+
if c == '}' {
|
|
172
|
+
// ASI applies before a closing brace regardless of line
|
|
173
|
+
// structure: `{ a(); }` → `{ a() }` stays valid.
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
if !sawNewline {
|
|
177
|
+
// Next token on the same line: ASI cannot fire without a line
|
|
178
|
+
// terminator, so the `;` separates the two constructs
|
|
179
|
+
// (`if (a) b(); else c();`, `do f(); while (x);`,
|
|
180
|
+
// `a = 1; /* note */ b = 2`). Keep it.
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
if c == '/' {
|
|
184
|
+
// bare `/` starts a regex literal or division, hazard.
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
switch c {
|
|
188
|
+
// If the next significant byte is one of these, dropping the terminator
|
|
189
|
+
// could let the following line re-associate with the prior expression.
|
|
190
|
+
// `( [`, a unary `+ -`, and a tagged-template backtick are the cases
|
|
191
|
+
// actually reachable from a valid statement start; `<` matters in .tsx
|
|
192
|
+
// (a leading `<` opens a JSX element). The remaining infix bytes cannot
|
|
193
|
+
// begin a valid statement on their own, but are listed defensively so
|
|
194
|
+
// the strip always cedes rather than risk a parse-changing edit.
|
|
195
|
+
case '[', '(', '`', '+', '-', '*', ',', '.', '<', '>', '=', '?', '%', '&', '|', '^':
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// scanPastTrivia advances from `pos` past whitespace and comments,
|
|
202
|
+
// returning the index of the next significant byte (len(src) at end of
|
|
203
|
+
// input) and whether a line terminator was crossed on the way. Both
|
|
204
|
+
// semicolon scanners (statement and member) share it so the ASI line
|
|
205
|
+
// rules cannot drift apart.
|
|
206
|
+
//
|
|
207
|
+
// A block comment that spans lines counts as a crossed line: per
|
|
208
|
+
// ECMA-262 (Comments), a multi-line comment containing a line
|
|
209
|
+
// terminator is treated as a line terminator for ASI, so the decision
|
|
210
|
+
// keys on comment content, not comment kind. `\r` counts as a line
|
|
211
|
+
// terminator on its own, which also covers CRLF sources.
|
|
212
|
+
func scanPastTrivia(src string, pos int) (next int, sawNewline bool) {
|
|
213
|
+
i := pos
|
|
214
|
+
for i < len(src) {
|
|
157
215
|
c := src[i]
|
|
158
|
-
if c == '
|
|
216
|
+
if c == '\n' || c == '\r' {
|
|
217
|
+
sawNewline = true
|
|
218
|
+
i++
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
if c == ' ' || c == '\t' {
|
|
222
|
+
i++
|
|
159
223
|
continue
|
|
160
224
|
}
|
|
161
225
|
if c == '/' && i+1 < len(src) {
|
|
162
226
|
if src[i+1] == '/' {
|
|
163
|
-
for i < len(src) && src[i] != '\n' {
|
|
227
|
+
for i < len(src) && src[i] != '\n' && src[i] != '\r' {
|
|
164
228
|
i++
|
|
165
229
|
}
|
|
166
230
|
continue
|
|
@@ -168,30 +232,22 @@ func nextStatementHasASIHazard(src string, end int) bool {
|
|
|
168
232
|
if src[i+1] == '*' {
|
|
169
233
|
i += 2
|
|
170
234
|
for i+1 < len(src) && !(src[i] == '*' && src[i+1] == '/') {
|
|
235
|
+
if src[i] == '\n' || src[i] == '\r' {
|
|
236
|
+
sawNewline = true
|
|
237
|
+
}
|
|
171
238
|
i++
|
|
172
239
|
}
|
|
173
240
|
if i+1 < len(src) {
|
|
174
|
-
i
|
|
241
|
+
i += 2 // step past '*/'
|
|
242
|
+
} else {
|
|
243
|
+
i = len(src) // unterminated block comment swallows the rest
|
|
175
244
|
}
|
|
176
245
|
continue
|
|
177
246
|
}
|
|
178
|
-
// bare `/` starts a regex literal or division, hazard.
|
|
179
|
-
return true
|
|
180
|
-
}
|
|
181
|
-
switch c {
|
|
182
|
-
// If the next significant byte is one of these, dropping the terminator
|
|
183
|
-
// could let the following line re-associate with the prior expression.
|
|
184
|
-
// `( [`, a unary `+ -`, and a tagged-template backtick are the cases
|
|
185
|
-
// actually reachable from a valid statement start; `<` matters in .tsx
|
|
186
|
-
// (a leading `<` opens a JSX element). The remaining infix bytes cannot
|
|
187
|
-
// begin a valid statement on their own, but are listed defensively so
|
|
188
|
-
// the strip always cedes rather than risk a parse-changing edit.
|
|
189
|
-
case '[', '(', '`', '+', '-', '*', ',', '.', '<', '>', '=', '?', '%', '&', '|', '^':
|
|
190
|
-
return true
|
|
191
247
|
}
|
|
192
|
-
return
|
|
248
|
+
return i, sawNewline
|
|
193
249
|
}
|
|
194
|
-
return
|
|
250
|
+
return len(src), sawNewline
|
|
195
251
|
}
|
|
196
252
|
|
|
197
253
|
// preferNeverSafeKind reports whether stripping the trailing semicolon
|
|
@@ -291,8 +347,9 @@ func stripMemberSemicolon(ctx *Context, src string, node *shimast.Node, isClassF
|
|
|
291
347
|
|
|
292
348
|
// memberSemicolonRedundant reports whether the member terminator `;`
|
|
293
349
|
// whose following byte is at `after` can be dropped without changing the
|
|
294
|
-
// parse. It scans past trivia (whitespace + comments
|
|
295
|
-
// significant byte and applies Prettier's
|
|
350
|
+
// parse. It scans past trivia (whitespace + comments, via
|
|
351
|
+
// scanPastTrivia) to the next significant byte and applies Prettier's
|
|
352
|
+
// semi:false member rules:
|
|
296
353
|
//
|
|
297
354
|
// - The closing `}` (or end of file) always makes the `;` redundant.
|
|
298
355
|
// - A next member on the SAME line (no newline crossed) keeps the `;`
|
|
@@ -305,57 +362,27 @@ func stripMemberSemicolon(ctx *Context, src string, node *shimast.Node, isClassF
|
|
|
305
362
|
// call/construct/generic signature (`(` / `<`) for type members
|
|
306
363
|
// (a leading `[` is an index signature there, not a continuation).
|
|
307
364
|
func memberSemicolonRedundant(src string, after int, isClassField bool) bool {
|
|
308
|
-
sawNewline :=
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
for i < len(src) && src[i] != '\n' {
|
|
323
|
-
i++
|
|
324
|
-
}
|
|
325
|
-
continue
|
|
326
|
-
}
|
|
327
|
-
if src[i+1] == '*' {
|
|
328
|
-
i += 2
|
|
329
|
-
for i+1 < len(src) && !(src[i] == '*' && src[i+1] == '/') {
|
|
330
|
-
if src[i] == '\n' {
|
|
331
|
-
sawNewline = true
|
|
332
|
-
}
|
|
333
|
-
i++
|
|
334
|
-
}
|
|
335
|
-
if i+1 < len(src) {
|
|
336
|
-
i += 2
|
|
337
|
-
}
|
|
338
|
-
continue
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if c == '}' {
|
|
342
|
-
return true
|
|
343
|
-
}
|
|
344
|
-
if !sawNewline {
|
|
365
|
+
i, sawNewline := scanPastTrivia(src, after)
|
|
366
|
+
if i >= len(src) {
|
|
367
|
+
return true
|
|
368
|
+
}
|
|
369
|
+
c := src[i]
|
|
370
|
+
if c == '}' {
|
|
371
|
+
return true
|
|
372
|
+
}
|
|
373
|
+
if !sawNewline {
|
|
374
|
+
return false
|
|
375
|
+
}
|
|
376
|
+
if isClassField {
|
|
377
|
+
switch c {
|
|
378
|
+
case '[', '(', '`', '+', '-', '*', '/', ',':
|
|
345
379
|
return false
|
|
346
380
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
switch c {
|
|
354
|
-
case '(', '<':
|
|
355
|
-
return false
|
|
356
|
-
}
|
|
381
|
+
} else {
|
|
382
|
+
switch c {
|
|
383
|
+
case '(', '<':
|
|
384
|
+
return false
|
|
357
385
|
}
|
|
358
|
-
return true
|
|
359
386
|
}
|
|
360
387
|
return true
|
|
361
388
|
}
|
|
@@ -27,11 +27,15 @@ const (
|
|
|
27
27
|
separatorPlaceholder = ""
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
// formatSortImportsOptions mirrors `ITtscLintFormatSortImports
|
|
30
|
+
// formatSortImportsOptions mirrors `ITtscLintFormatSortImports`, plus the
|
|
31
|
+
// top-level `endOfLine` the config layer threads in so the rebuilt block
|
|
32
|
+
// joins declarations with the file's line ending.
|
|
31
33
|
type formatSortImportsOptions struct {
|
|
32
|
-
Order
|
|
33
|
-
CaseSensitive
|
|
34
|
-
CombineTypeAndValue
|
|
34
|
+
Order []string `json:"order"`
|
|
35
|
+
CaseSensitive bool `json:"caseSensitive"`
|
|
36
|
+
CombineTypeAndValue bool `json:"combineTypeAndValue"`
|
|
37
|
+
UnsafeSortRuntimeImports bool `json:"unsafeSortRuntimeImports"`
|
|
38
|
+
EndOfLine *string `json:"endOfLine"`
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
// defaultImportOrder is used when the user supplies no `order`: Node built-ins,
|
|
@@ -44,12 +48,13 @@ var defaultImportOrder = []string{
|
|
|
44
48
|
`^[.]`,
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
// formatSortImports
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
51
|
+
// formatSortImports safely sorts named specifiers and erased type-only import
|
|
52
|
+
// blocks. Runtime-bearing declarations retain source order unless the user
|
|
53
|
+
// explicitly enables `unsafeSortRuntimeImports`; only then are they grouped,
|
|
54
|
+
// alphabetized, and merged. When that unsafe mode and `combineTypeAndValue`
|
|
55
|
+
// are both on, a type-only import may fold into a value import of the same
|
|
56
|
+
// module. Groups are user-configurable via the `order` option; when omitted,
|
|
57
|
+
// the rule falls back to {@link defaultImportOrder}.
|
|
53
58
|
//
|
|
54
59
|
// Within each group, declarations are sorted by their module-specifier text
|
|
55
60
|
// (ASCII order, or case-insensitive unless `caseSensitive: true`). Named
|
|
@@ -58,10 +63,10 @@ var defaultImportOrder = []string{
|
|
|
58
63
|
// Safety policy: if any byte between the contiguous imports is not
|
|
59
64
|
// whitespace, the rule bails. Comments anchored to specific imports would
|
|
60
65
|
// otherwise move with the wrong declaration, which is a strictly worse
|
|
61
|
-
// outcome than declining to sort.
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
type formatSortImports struct{}
|
|
66
|
+
// outcome than declining to sort. Every import other than `import type` can
|
|
67
|
+
// evaluate a module, including default, namespace, and named binding imports,
|
|
68
|
+
// so declaration-level rewriting is disabled for those blocks by default.
|
|
69
|
+
type formatSortImports struct{ optionsRule }
|
|
65
70
|
|
|
66
71
|
func (formatSortImports) Name() string { return "format/sort-imports" }
|
|
67
72
|
func (formatSortImports) IsFormat() bool { return true }
|
|
@@ -80,19 +85,15 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
|
|
|
80
85
|
return
|
|
81
86
|
}
|
|
82
87
|
imports := collectLeadingImports(statements.Nodes)
|
|
83
|
-
// Block-level
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
// across a sibling import that depends on the polyfill being
|
|
90
|
-
// initialized first would silently change runtime behavior. The
|
|
91
|
-
// rule conservatively refuses to reorder the entire block in that
|
|
92
|
-
// case.
|
|
88
|
+
// Block-level rewriting runs only for erased type-only imports or after the
|
|
89
|
+
// caller explicitly accepts runtime reordering. Binding imports execute
|
|
90
|
+
// their dependencies just like bare imports, so preserving only
|
|
91
|
+
// `import "./polyfill"` is insufficient. Comment trivia remains an
|
|
92
|
+
// unconditional barrier even in unsafe mode because the rebuilder cannot
|
|
93
|
+
// preserve a comment's declaration attachment.
|
|
93
94
|
if len(imports) >= 2 &&
|
|
94
95
|
leadingTriviaIsAllWhitespace(src, imports) &&
|
|
95
|
-
!
|
|
96
|
+
(opts.unsafeSortRuntimeImports || !importsHaveRuntimeEvaluation(imports)) {
|
|
96
97
|
first := imports[0]
|
|
97
98
|
last := imports[len(imports)-1]
|
|
98
99
|
replaceStart := shimscanner.SkipTrivia(src, first.Pos())
|
|
@@ -118,9 +119,14 @@ func (formatSortImports) Check(ctx *Context, node *shimast.Node) {
|
|
|
118
119
|
// during one Check call. All option defaults are applied here so the
|
|
119
120
|
// rest of the rule code does not branch on nil-ness.
|
|
120
121
|
type resolvedSortImportsOptions struct {
|
|
121
|
-
groups
|
|
122
|
-
caseSensitive
|
|
123
|
-
combineTypeAndValue
|
|
122
|
+
groups []sortImportsGroup
|
|
123
|
+
caseSensitive bool
|
|
124
|
+
combineTypeAndValue bool
|
|
125
|
+
unsafeSortRuntimeImports bool
|
|
126
|
+
// eol is the newline joined between rebuilt declarations: `"\n"` by
|
|
127
|
+
// default, `"\r\n"` under endOfLine:"crlf". Verbatim declaration text is
|
|
128
|
+
// preserved as-is; only the synthesized inter-declaration joins use it.
|
|
129
|
+
eol string
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
// sortImportsGroup is one resolved entry of the `order` array. A separator
|
|
@@ -143,10 +149,16 @@ func loadSortImportsOptions(ctx *Context) resolvedSortImportsOptions {
|
|
|
143
149
|
order = defaultImportOrder
|
|
144
150
|
}
|
|
145
151
|
groups := parseImportOrder(order)
|
|
152
|
+
eol := "\n"
|
|
153
|
+
if raw.EndOfLine != nil && *raw.EndOfLine == "crlf" {
|
|
154
|
+
eol = "\r\n"
|
|
155
|
+
}
|
|
146
156
|
return resolvedSortImportsOptions{
|
|
147
|
-
groups:
|
|
148
|
-
caseSensitive:
|
|
149
|
-
combineTypeAndValue:
|
|
157
|
+
groups: groups,
|
|
158
|
+
caseSensitive: raw.CaseSensitive,
|
|
159
|
+
combineTypeAndValue: raw.CombineTypeAndValue,
|
|
160
|
+
unsafeSortRuntimeImports: raw.UnsafeSortRuntimeImports,
|
|
161
|
+
eol: eol,
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
164
|
|
|
@@ -298,21 +310,22 @@ func specifierListHasCommentTrivia(src string, specifiers []*shimast.Node) bool
|
|
|
298
310
|
return false
|
|
299
311
|
}
|
|
300
312
|
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
func
|
|
313
|
+
// importsHaveRuntimeEvaluation reports whether a contiguous block contains an
|
|
314
|
+
// import that survives as a module dependency. Bare, default, namespace,
|
|
315
|
+
// named, and deferred imports can all trigger top-level evaluation; only a
|
|
316
|
+
// clause explicitly marked `type` is erased. Unexpected AST shapes are treated
|
|
317
|
+
// as runtime-bearing so a parser change cannot silently weaken the guard.
|
|
318
|
+
func importsHaveRuntimeEvaluation(imports []*shimast.Node) bool {
|
|
307
319
|
for _, decl := range imports {
|
|
308
320
|
if decl == nil {
|
|
309
|
-
|
|
321
|
+
return true
|
|
310
322
|
}
|
|
311
323
|
imp := decl.AsImportDeclaration()
|
|
312
|
-
if imp == nil {
|
|
313
|
-
|
|
324
|
+
if imp == nil || imp.ImportClause == nil {
|
|
325
|
+
return true
|
|
314
326
|
}
|
|
315
|
-
|
|
327
|
+
clause := imp.ImportClause.AsImportClause()
|
|
328
|
+
if clause == nil || clause.PhaseModifier != shimast.KindTypeKeyword {
|
|
316
329
|
return true
|
|
317
330
|
}
|
|
318
331
|
}
|
|
@@ -420,9 +433,11 @@ func buildSortedImportBlock(src string, imports []*shimast.Node, opts resolvedSo
|
|
|
420
433
|
for i, e := range entries {
|
|
421
434
|
if i > 0 {
|
|
422
435
|
if e.group != prevGroup && separatorBetween(opts.groups, prevGroup, e.group) {
|
|
423
|
-
|
|
436
|
+
// A blank line between groups is two line endings.
|
|
437
|
+
b.WriteString(opts.eol)
|
|
438
|
+
b.WriteString(opts.eol)
|
|
424
439
|
} else {
|
|
425
|
-
b.WriteString(
|
|
440
|
+
b.WriteString(opts.eol)
|
|
426
441
|
}
|
|
427
442
|
}
|
|
428
443
|
b.WriteString(e.text)
|
|
@@ -594,14 +609,25 @@ func originalEntry(d siDecl) siEntry {
|
|
|
594
609
|
// renderMergedDecl folds a bucket of mergeable declarations into one statement.
|
|
595
610
|
// It returns the rendered text, whether the merged result is a type-only import,
|
|
596
611
|
// and an `ok` flag that is false when the declarations cannot be merged
|
|
597
|
-
// (conflicting default names,
|
|
598
|
-
// mixed value import
|
|
612
|
+
// (conflicting default names, a type-only default that cannot survive in a
|
|
613
|
+
// mixed value import, a type-only default alongside named bindings, or a
|
|
614
|
+
// bucket containing a namespace or comment-bearing declaration).
|
|
599
615
|
func renderMergedDecl(group []siDecl, opts resolvedSortImportsOptions) (string, bool, bool) {
|
|
600
616
|
mergedTypeOnly := true
|
|
601
617
|
for _, d := range group {
|
|
602
618
|
if !d.typeOnly {
|
|
603
619
|
mergedTypeOnly = false
|
|
604
620
|
}
|
|
621
|
+
// Namespace bindings and comment-bearing declarations are never mergeable:
|
|
622
|
+
// the rebuilt statement carries no `* as ns` slot and would drop comment
|
|
623
|
+
// bytes. mergeKey normally isolates them in per-declaration buckets, but
|
|
624
|
+
// its key embeds the original text, so byte-identical declarations (a
|
|
625
|
+
// duplicate-binding error TypeScript reports later, which the parse-level
|
|
626
|
+
// formatter still sees) collide into one bucket. Refuse here so the
|
|
627
|
+
// originals are re-emitted verbatim instead of silently losing bindings.
|
|
628
|
+
if d.namespace || d.hasSpecComment {
|
|
629
|
+
return "", false, false
|
|
630
|
+
}
|
|
605
631
|
}
|
|
606
632
|
defaultName := ""
|
|
607
633
|
defaultTypeOnly := false
|
|
@@ -624,6 +650,14 @@ func renderMergedDecl(group []siDecl, opts resolvedSortImportsOptions) (string,
|
|
|
624
650
|
if defaultName == "" && len(specs) == 0 {
|
|
625
651
|
return "", false, false
|
|
626
652
|
}
|
|
653
|
+
// A type-only import cannot carry both a default binding and named bindings:
|
|
654
|
+
// `import type D, { A } from "m"` is a syntax error (TS1363). Refuse the
|
|
655
|
+
// merge so the declarations stay separate. (A type-only default with an
|
|
656
|
+
// empty merged spec list still folds to `import type D from "m"`, which is
|
|
657
|
+
// legal.)
|
|
658
|
+
if mergedTypeOnly && defaultName != "" && len(specs) > 0 {
|
|
659
|
+
return "", false, false
|
|
660
|
+
}
|
|
627
661
|
|
|
628
662
|
var b strings.Builder
|
|
629
663
|
b.WriteString("import ")
|
|
@@ -36,7 +36,7 @@ import (
|
|
|
36
36
|
//
|
|
37
37
|
// Idempotent: once each statement is alone on its line, step 2 abstains
|
|
38
38
|
// for all of them and the rule emits nothing.
|
|
39
|
-
type formatStatementSplit struct{}
|
|
39
|
+
type formatStatementSplit struct{ optionsRule }
|
|
40
40
|
|
|
41
41
|
// formatStatementSplitOptions carries the indentation + EOL settings the
|
|
42
42
|
// rule needs to synthesize the inserted line break. The JSON tags match
|
|
@@ -19,7 +19,7 @@ import (
|
|
|
19
19
|
// so it is a normalization rule rather than part of the print-width
|
|
20
20
|
// reflow. Idempotent: a wrapped operand parses as a parenthesized
|
|
21
21
|
// expression, not a bare `??`, so the next pass leaves it alone.
|
|
22
|
-
type formatTernaryNullishParens struct{}
|
|
22
|
+
type formatTernaryNullishParens struct{ optionsRule }
|
|
23
23
|
|
|
24
24
|
func (formatTernaryNullishParens) Name() string { return "format/ternary-nullish-parens" }
|
|
25
25
|
func (formatTernaryNullishParens) IsFormat() bool { return true }
|
|
@@ -35,12 +35,22 @@ import (
|
|
|
35
35
|
// element. The same restriction applies to rest binding patterns, which
|
|
36
36
|
// the rule does not visit at all.
|
|
37
37
|
//
|
|
38
|
+
// Destructuring assignment TARGETS are the one array/object-literal shape
|
|
39
|
+
// where the same rest restriction bites: `({ a, ...rest } = obj)` and
|
|
40
|
+
// `[a, ...rest] = arr` parse as ObjectLiteralExpression /
|
|
41
|
+
// ArrayLiteralExpression (not binding patterns) and are therefore visited,
|
|
42
|
+
// yet a trailing comma after their `AssignmentRestProperty` /
|
|
43
|
+
// `AssignmentRestElement` is a syntax error. `isRestAssignmentTargetLiteral`
|
|
44
|
+
// suppresses the insert for exactly those; a real value literal with a
|
|
45
|
+
// trailing spread (`{ a, ...o }`) and a non-rest target (`{ a, b } = obj`)
|
|
46
|
+
// both legally keep the comma.
|
|
47
|
+
//
|
|
38
48
|
// Unparenthesized arrow parameters (`a => …`) are also skipped: there is
|
|
39
49
|
// no parameter-list paren to anchor the comma against, and ECMAScript has
|
|
40
50
|
// no place to insert one. `findCloseTokenAfter` bails on the first
|
|
41
51
|
// non-trivia byte after the parameter's `End()` (the `=>` token), so the
|
|
42
52
|
// rule abstains without emitting an edit.
|
|
43
|
-
type formatTrailingComma struct{}
|
|
53
|
+
type formatTrailingComma struct{ optionsRule }
|
|
44
54
|
|
|
45
55
|
// formatTrailingCommaOptions mirrors `TtscLintRuleOptions.TrailingComma`.
|
|
46
56
|
type formatTrailingCommaOptions struct {
|
|
@@ -94,12 +104,18 @@ func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
|
94
104
|
if arr == nil {
|
|
95
105
|
return
|
|
96
106
|
}
|
|
107
|
+
if isRestAssignmentTargetLiteral(node) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
97
110
|
considerTrailingComma(ctx, arr.Elements, node.End()-1)
|
|
98
111
|
case shimast.KindObjectLiteralExpression:
|
|
99
112
|
obj := node.AsObjectLiteralExpression()
|
|
100
113
|
if obj == nil {
|
|
101
114
|
return
|
|
102
115
|
}
|
|
116
|
+
if isRestAssignmentTargetLiteral(node) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
103
119
|
considerTrailingComma(ctx, obj.Properties, node.End()-1)
|
|
104
120
|
case shimast.KindCallExpression:
|
|
105
121
|
if mode == "es5" {
|
|
@@ -351,6 +367,53 @@ func considerFunctionParameterComma(ctx *Context, list *shimast.NodeList) {
|
|
|
351
367
|
considerTrailingComma(ctx, list, closePos)
|
|
352
368
|
}
|
|
353
369
|
|
|
370
|
+
// isRestAssignmentTargetLiteral reports whether node is an object- or
|
|
371
|
+
// array-literal used as a destructuring ASSIGNMENT TARGET whose last
|
|
372
|
+
// element is a rest (`...x`). ECMAScript forbids a trailing comma after an
|
|
373
|
+
// `AssignmentRestElement` / `AssignmentRestProperty`, so neither this rule
|
|
374
|
+
// nor the print-width printer (which shares this helper) may add one there.
|
|
375
|
+
//
|
|
376
|
+
// The two-pronged guard is what keeps it from over-suppressing. Only a
|
|
377
|
+
// destructuring assignment target that ENDS in a rest is illegal:
|
|
378
|
+
//
|
|
379
|
+
// - A real value literal with a trailing spread (`{ a, ...o }`,
|
|
380
|
+
// `[a, ...rest]`) is not a target, so isDestructuringAssignmentTarget
|
|
381
|
+
// returns false and the comma stays legal.
|
|
382
|
+
// - A non-rest assignment target (`{ a, b } = obj`) fails the
|
|
383
|
+
// last-element-is-rest check and keeps its comma.
|
|
384
|
+
//
|
|
385
|
+
// It handles nested targets (`[{ a, ...rest }] = arr`,
|
|
386
|
+
// `({ x: [a, ...rest] } = obj)`) and for-of/for-in assignment initializers
|
|
387
|
+
// through isDestructuringAssignmentTarget's ancestor walk.
|
|
388
|
+
func isRestAssignmentTargetLiteral(node *shimast.Node) bool {
|
|
389
|
+
if node == nil {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
switch node.Kind {
|
|
393
|
+
case shimast.KindArrayLiteralExpression:
|
|
394
|
+
arr := node.AsArrayLiteralExpression()
|
|
395
|
+
if arr == nil || arr.Elements == nil || len(arr.Elements.Nodes) == 0 {
|
|
396
|
+
return false
|
|
397
|
+
}
|
|
398
|
+
last := arr.Elements.Nodes[len(arr.Elements.Nodes)-1]
|
|
399
|
+
if last == nil || last.Kind != shimast.KindSpreadElement {
|
|
400
|
+
return false
|
|
401
|
+
}
|
|
402
|
+
case shimast.KindObjectLiteralExpression:
|
|
403
|
+
obj := node.AsObjectLiteralExpression()
|
|
404
|
+
if obj == nil || obj.Properties == nil || len(obj.Properties.Nodes) == 0 {
|
|
405
|
+
return false
|
|
406
|
+
}
|
|
407
|
+
last := obj.Properties.Nodes[len(obj.Properties.Nodes)-1]
|
|
408
|
+
if last == nil || last.Kind != shimast.KindSpreadAssignment {
|
|
409
|
+
return false
|
|
410
|
+
}
|
|
411
|
+
default:
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
return isDestructuringAssignmentTarget(node)
|
|
415
|
+
}
|
|
416
|
+
|
|
354
417
|
// lastParameterIsRest reports whether the parameter list ends with a
|
|
355
418
|
// rest element (`...rest`). ECMAScript syntax disallows a trailing
|
|
356
419
|
// comma after a rest element; the rule must not insert one.
|
|
@@ -34,7 +34,7 @@ import (
|
|
|
34
34
|
// Idempotent: a file that already has no trailing whitespace, at most
|
|
35
35
|
// one consecutive blank line, no leading/trailing blank lines, and a
|
|
36
36
|
// single final EOL produces no edits.
|
|
37
|
-
type formatWhitespace struct{}
|
|
37
|
+
type formatWhitespace struct{ optionsRule }
|
|
38
38
|
|
|
39
39
|
// formatWhitespaceOptions carries only the EOL setting; the rule needs
|
|
40
40
|
// it to synthesize the file's final newline. Indentation is irrelevant
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"regexp"
|
|
5
4
|
"strings"
|
|
6
5
|
|
|
7
6
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
8
7
|
)
|
|
9
8
|
|
|
10
|
-
type functionalParameters struct{}
|
|
11
|
-
type functionalImmutableData struct{}
|
|
12
|
-
type functionalNoClassInheritance struct{}
|
|
13
|
-
type functionalNoClasses struct{}
|
|
14
|
-
type functionalNoConditionalStatements struct{}
|
|
15
|
-
type functionalNoExpressionStatements struct{}
|
|
16
|
-
type functionalNoLet struct{}
|
|
17
|
-
type functionalNoLoopStatements struct{}
|
|
18
|
-
type functionalNoMixedTypes struct{}
|
|
19
|
-
type functionalNoPromiseReject struct{}
|
|
20
|
-
type functionalNoReturnVoid struct{}
|
|
21
|
-
type functionalNoThisExpressions struct{}
|
|
22
|
-
type functionalNoThrowStatements struct{}
|
|
23
|
-
type functionalNoTryStatements struct{}
|
|
24
|
-
type functionalPreferImmutableTypes struct{}
|
|
25
|
-
type functionalPreferPropertySignatures struct{}
|
|
26
|
-
type functionalPreferReadonlyType struct{}
|
|
27
|
-
type functionalPreferTacit struct{}
|
|
28
|
-
type functionalReadonlyType struct{}
|
|
29
|
-
type functionalTypeDeclarationImmutability struct{}
|
|
9
|
+
type functionalParameters struct{ optionsRule }
|
|
10
|
+
type functionalImmutableData struct{ optionsRule }
|
|
11
|
+
type functionalNoClassInheritance struct{ optionsRule }
|
|
12
|
+
type functionalNoClasses struct{ optionsRule }
|
|
13
|
+
type functionalNoConditionalStatements struct{ optionsRule }
|
|
14
|
+
type functionalNoExpressionStatements struct{ optionsRule }
|
|
15
|
+
type functionalNoLet struct{ optionsRule }
|
|
16
|
+
type functionalNoLoopStatements struct{ optionsRule }
|
|
17
|
+
type functionalNoMixedTypes struct{ optionsRule }
|
|
18
|
+
type functionalNoPromiseReject struct{ optionsRule }
|
|
19
|
+
type functionalNoReturnVoid struct{ optionsRule }
|
|
20
|
+
type functionalNoThisExpressions struct{ optionsRule }
|
|
21
|
+
type functionalNoThrowStatements struct{ optionsRule }
|
|
22
|
+
type functionalNoTryStatements struct{ optionsRule }
|
|
23
|
+
type functionalPreferImmutableTypes struct{ optionsRule }
|
|
24
|
+
type functionalPreferPropertySignatures struct{ optionsRule }
|
|
25
|
+
type functionalPreferReadonlyType struct{ optionsRule }
|
|
26
|
+
type functionalPreferTacit struct{ optionsRule }
|
|
27
|
+
type functionalReadonlyType struct{ optionsRule }
|
|
28
|
+
type functionalTypeDeclarationImmutability struct{ optionsRule }
|
|
30
29
|
|
|
31
30
|
func (functionalParameters) Name() string { return "functional/functional-parameters" }
|
|
32
31
|
func (functionalImmutableData) Name() string { return "functional/immutable-data" }
|
|
@@ -921,7 +920,7 @@ func functionalPatternMatches(pattern, name string) bool {
|
|
|
921
920
|
if pattern == name {
|
|
922
921
|
return true
|
|
923
922
|
}
|
|
924
|
-
re, err :=
|
|
923
|
+
re, err := compileUserPattern(pattern)
|
|
925
924
|
return err == nil && re.MatchString(name)
|
|
926
925
|
}
|
|
927
926
|
|