@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
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// typescript/ban-ts-comment: a faithful port of typescript-eslint's
|
|
2
|
+
// ban-ts-comment rule, including its recommended defaults, per-directive
|
|
3
|
+
// options, description thresholds, and description-format matching.
|
|
4
|
+
//
|
|
5
|
+
// The rule scans every parser-classified comment token (via
|
|
6
|
+
// forEachCommentToken)
|
|
7
|
+
// instead of reading `SourceFile.CommentDirectives`: the compiler-side
|
|
8
|
+
// list only carries the error-suppression directives (`@ts-expect-error`,
|
|
9
|
+
// `@ts-ignore`) and would miss `@ts-nocheck` / `@ts-check` entirely.
|
|
10
|
+
//
|
|
11
|
+
// Directive recognition mirrors the upstream rule (which in turn mirrors
|
|
12
|
+
// the TypeScript compiler's own regexes):
|
|
13
|
+
//
|
|
14
|
+
// - `@ts-check` / `@ts-nocheck` are pragmas: line comments with two or
|
|
15
|
+
// three leading slashes only. Block comments never activate them.
|
|
16
|
+
// - `@ts-expect-error` / `@ts-ignore` match line comments (any number
|
|
17
|
+
// of leading slashes) and the LAST line of a block comment — the only
|
|
18
|
+
// line the compiler honors.
|
|
19
|
+
// - `@ts-nocheck` is skipped when it appears at or after the first
|
|
20
|
+
// statement's line, because the compiler ignores it there.
|
|
21
|
+
//
|
|
22
|
+
// Reference: https://typescript-eslint.io/rules/ban-ts-comment
|
|
23
|
+
package linthost
|
|
24
|
+
|
|
25
|
+
import (
|
|
26
|
+
"bytes"
|
|
27
|
+
"encoding/json"
|
|
28
|
+
"fmt"
|
|
29
|
+
"regexp"
|
|
30
|
+
"strings"
|
|
31
|
+
|
|
32
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
33
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// tsCommentJSWhitespace is the ECMAScript `\s` character class. The
|
|
37
|
+
// upstream regexes run under JavaScript semantics where `\s` covers the
|
|
38
|
+
// Unicode space separators and the BOM; Go's `\s` is ASCII-only, so the
|
|
39
|
+
// class is spelled out to keep the port byte-faithful.
|
|
40
|
+
const tsCommentJSWhitespace = `[\t\n\v\f\r \x{00A0}\x{1680}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}\x{FEFF}]`
|
|
41
|
+
|
|
42
|
+
var (
|
|
43
|
+
// tsPragmaLineRegex matches the `@ts-check` / `@ts-nocheck` pragma form
|
|
44
|
+
// against the FULL line-comment text (`//...`): two or three leading
|
|
45
|
+
// slashes, optional whitespace, then the directive. Mirrors upstream's
|
|
46
|
+
// singleLinePragmaRegEx applied to `'//' + comment.value`.
|
|
47
|
+
tsPragmaLineRegex = regexp.MustCompile(`^///?` + tsCommentJSWhitespace + `*@ts-(?P<directive>check|nocheck)(?P<description>.*)$`)
|
|
48
|
+
|
|
49
|
+
// tsDirectiveLineRegex matches `@ts-expect-error` / `@ts-ignore` against
|
|
50
|
+
// a line comment's value (text after the opening `//`): any number of
|
|
51
|
+
// extra slashes, optional whitespace, then the directive.
|
|
52
|
+
tsDirectiveLineRegex = regexp.MustCompile(`^/*` + tsCommentJSWhitespace + `*@ts-(?P<directive>expect-error|ignore)(?P<description>.*)`)
|
|
53
|
+
|
|
54
|
+
// tsDirectiveBlockRegex matches `@ts-expect-error` / `@ts-ignore`
|
|
55
|
+
// against the last line of a block comment's value.
|
|
56
|
+
tsDirectiveBlockRegex = regexp.MustCompile(`^` + tsCommentJSWhitespace + `*(?:/|\*)*` + tsCommentJSWhitespace + `*@ts-(?P<directive>expect-error|ignore)(?P<description>.*)`)
|
|
57
|
+
|
|
58
|
+
// tsCommentLineBreakRegex splits block-comment values on the ECMAScript
|
|
59
|
+
// line terminators (upstream LINEBREAK_MATCHER).
|
|
60
|
+
tsCommentLineBreakRegex = regexp.MustCompile(`\r\n|[\r\n\x{2028}\x{2029}]`)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// tsCommentDirective is one recognized directive with the raw description
|
|
64
|
+
// text that follows it (untrimmed, exactly as the upstream regex captures
|
|
65
|
+
// it — descriptionFormat patterns match against this raw text).
|
|
66
|
+
type tsCommentDirective struct {
|
|
67
|
+
directive string // "check" | "nocheck" | "expect-error" | "ignore"
|
|
68
|
+
description string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// findTsDirectiveInComment classifies one comment token. `raw` is the full
|
|
72
|
+
// comment text including delimiters. Returns nil when the comment carries
|
|
73
|
+
// no supported directive.
|
|
74
|
+
func findTsDirectiveInComment(kind shimast.Kind, raw string) *tsCommentDirective {
|
|
75
|
+
if kind == shimast.KindSingleLineCommentTrivia {
|
|
76
|
+
if match := tsPragmaLineRegex.FindStringSubmatch(raw); match != nil {
|
|
77
|
+
return &tsCommentDirective{directive: match[1], description: match[2]}
|
|
78
|
+
}
|
|
79
|
+
if match := tsDirectiveLineRegex.FindStringSubmatch(raw[2:]); match != nil {
|
|
80
|
+
return &tsCommentDirective{directive: match[1], description: match[2]}
|
|
81
|
+
}
|
|
82
|
+
return nil
|
|
83
|
+
}
|
|
84
|
+
// Block comment: only the last line can carry an effective directive,
|
|
85
|
+
// matching the compiler's scanner (and the upstream rule).
|
|
86
|
+
value := strings.TrimSuffix(raw[2:], "*/")
|
|
87
|
+
lines := tsCommentLineBreakRegex.Split(value, -1)
|
|
88
|
+
if match := tsDirectiveBlockRegex.FindStringSubmatch(lines[len(lines)-1]); match != nil {
|
|
89
|
+
return &tsCommentDirective{directive: match[1], description: match[2]}
|
|
90
|
+
}
|
|
91
|
+
return nil
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// tsDirectivePolicy is the normalized evaluation mode for one directive.
|
|
95
|
+
// At most one of `ban` and `requireDescription` is set; `format` is only
|
|
96
|
+
// non-nil when `requireDescription` is set.
|
|
97
|
+
type tsDirectivePolicy struct {
|
|
98
|
+
ban bool
|
|
99
|
+
requireDescription bool
|
|
100
|
+
format *regexp.Regexp
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// banTsCommentOptions is the raw wire shape of the rule's options object.
|
|
104
|
+
// Directive values stay as raw JSON because each accepts a boolean,
|
|
105
|
+
// the string "allow-with-description", or `{ descriptionFormat }` — the
|
|
106
|
+
// upstream DirectiveConfig union.
|
|
107
|
+
type banTsCommentOptions struct {
|
|
108
|
+
MinimumDescriptionLength *int `json:"minimumDescriptionLength"`
|
|
109
|
+
TsCheck json.RawMessage `json:"ts-check"`
|
|
110
|
+
TsExpectError json.RawMessage `json:"ts-expect-error"`
|
|
111
|
+
TsIgnore json.RawMessage `json:"ts-ignore"`
|
|
112
|
+
TsNocheck json.RawMessage `json:"ts-nocheck"`
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// resolvedBanTsCommentOptions is the per-run policy table after merging
|
|
116
|
+
// user options over the upstream recommended defaults.
|
|
117
|
+
type resolvedBanTsCommentOptions struct {
|
|
118
|
+
minimumDescriptionLength int
|
|
119
|
+
check tsDirectivePolicy
|
|
120
|
+
expectError tsDirectivePolicy
|
|
121
|
+
ignore tsDirectivePolicy
|
|
122
|
+
nocheck tsDirectivePolicy
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func (o resolvedBanTsCommentOptions) policy(directive string) tsDirectivePolicy {
|
|
126
|
+
switch directive {
|
|
127
|
+
case "check":
|
|
128
|
+
return o.check
|
|
129
|
+
case "expect-error":
|
|
130
|
+
return o.expectError
|
|
131
|
+
case "ignore":
|
|
132
|
+
return o.ignore
|
|
133
|
+
case "nocheck":
|
|
134
|
+
return o.nocheck
|
|
135
|
+
}
|
|
136
|
+
return tsDirectivePolicy{}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// resolveBanTsCommentOptions merges the decoded options over the upstream
|
|
140
|
+
// recommended defaults:
|
|
141
|
+
//
|
|
142
|
+
// {
|
|
143
|
+
// minimumDescriptionLength: 3,
|
|
144
|
+
// "ts-check": false,
|
|
145
|
+
// "ts-expect-error": "allow-with-description",
|
|
146
|
+
// "ts-ignore": true,
|
|
147
|
+
// "ts-nocheck": true,
|
|
148
|
+
// }
|
|
149
|
+
//
|
|
150
|
+
// Absent keys keep their default; present keys replace it wholesale, the
|
|
151
|
+
// same per-key merge ESLint applies to defaultOptions.
|
|
152
|
+
func resolveBanTsCommentOptions(raw banTsCommentOptions) resolvedBanTsCommentOptions {
|
|
153
|
+
resolved := resolvedBanTsCommentOptions{
|
|
154
|
+
minimumDescriptionLength: 3,
|
|
155
|
+
check: tsDirectivePolicy{},
|
|
156
|
+
expectError: tsDirectivePolicy{requireDescription: true},
|
|
157
|
+
ignore: tsDirectivePolicy{ban: true},
|
|
158
|
+
nocheck: tsDirectivePolicy{ban: true},
|
|
159
|
+
}
|
|
160
|
+
if raw.MinimumDescriptionLength != nil {
|
|
161
|
+
resolved.minimumDescriptionLength = *raw.MinimumDescriptionLength
|
|
162
|
+
}
|
|
163
|
+
resolved.check = normalizeTsDirectiveConfig(raw.TsCheck, resolved.check)
|
|
164
|
+
resolved.expectError = normalizeTsDirectiveConfig(raw.TsExpectError, resolved.expectError)
|
|
165
|
+
resolved.ignore = normalizeTsDirectiveConfig(raw.TsIgnore, resolved.ignore)
|
|
166
|
+
resolved.nocheck = normalizeTsDirectiveConfig(raw.TsNocheck, resolved.nocheck)
|
|
167
|
+
return resolved
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// normalizeTsDirectiveConfig turns one raw DirectiveConfig value into a
|
|
171
|
+
// policy. Upstream validates the shape through its JSON schema and rejects
|
|
172
|
+
// the whole config on mismatch; this host has no schema layer, so a value
|
|
173
|
+
// outside the documented union quietly keeps the directive's documented
|
|
174
|
+
// default rather than inventing a stricter policy.
|
|
175
|
+
func normalizeTsDirectiveConfig(raw json.RawMessage, def tsDirectivePolicy) tsDirectivePolicy {
|
|
176
|
+
if len(raw) == 0 || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
177
|
+
return def
|
|
178
|
+
}
|
|
179
|
+
var flag bool
|
|
180
|
+
if json.Unmarshal(raw, &flag) == nil {
|
|
181
|
+
return tsDirectivePolicy{ban: flag}
|
|
182
|
+
}
|
|
183
|
+
var literal string
|
|
184
|
+
if json.Unmarshal(raw, &literal) == nil {
|
|
185
|
+
if literal == "allow-with-description" {
|
|
186
|
+
return tsDirectivePolicy{requireDescription: true}
|
|
187
|
+
}
|
|
188
|
+
return def
|
|
189
|
+
}
|
|
190
|
+
var object struct {
|
|
191
|
+
DescriptionFormat *string `json:"descriptionFormat"`
|
|
192
|
+
}
|
|
193
|
+
if json.Unmarshal(raw, &object) == nil {
|
|
194
|
+
// Upstream treats an object without a truthy descriptionFormat as
|
|
195
|
+
// plain allowance: it neither bans nor demands a description.
|
|
196
|
+
if object.DescriptionFormat == nil || *object.DescriptionFormat == "" {
|
|
197
|
+
return tsDirectivePolicy{}
|
|
198
|
+
}
|
|
199
|
+
format, err := regexp.Compile(*object.DescriptionFormat)
|
|
200
|
+
if err != nil {
|
|
201
|
+
// The format cannot be enforced (upstream would have thrown while
|
|
202
|
+
// constructing the RegExp). Keep the description-length gate the
|
|
203
|
+
// object form implies instead of silently allowing everything.
|
|
204
|
+
return tsDirectivePolicy{requireDescription: true}
|
|
205
|
+
}
|
|
206
|
+
return tsDirectivePolicy{requireDescription: true, format: format}
|
|
207
|
+
}
|
|
208
|
+
return def
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// banTsComment implements typescript/ban-ts-comment.
|
|
212
|
+
type banTsComment struct{ optionsRule }
|
|
213
|
+
|
|
214
|
+
func (banTsComment) Name() string { return "typescript/ban-ts-comment" }
|
|
215
|
+
func (banTsComment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
216
|
+
|
|
217
|
+
func (banTsComment) Check(ctx *Context, node *shimast.Node) {
|
|
218
|
+
if ctx.File == nil {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
text := ctx.File.Text()
|
|
222
|
+
// Cheap short-circuit: every supported directive contains this
|
|
223
|
+
// substring, so directive-free files skip the comment re-scan entirely
|
|
224
|
+
// (same strategy as parseLintInlineDirectives).
|
|
225
|
+
if !strings.Contains(text, "@ts-") {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
var raw banTsCommentOptions
|
|
230
|
+
if err := ctx.DecodeOptions(&raw); err != nil {
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
options := resolveBanTsCommentOptions(raw)
|
|
234
|
+
|
|
235
|
+
// The compiler only honors `@ts-nocheck` before the first statement;
|
|
236
|
+
// upstream compares source lines, so a directive on the same line as
|
|
237
|
+
// the first statement is inert too and stays unreported.
|
|
238
|
+
firstStatementLine := -1
|
|
239
|
+
if statements := ctx.File.Statements; statements != nil && len(statements.Nodes) > 0 && statements.Nodes[0] != nil {
|
|
240
|
+
pos := shimscanner.SkipTrivia(text, statements.Nodes[0].Pos())
|
|
241
|
+
firstStatementLine = shimscanner.GetECMALineOfPosition(ctx.File, pos)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
forEachCommentToken(ctx.File, func(kind shimast.Kind, pos, end int) {
|
|
245
|
+
matched := findTsDirectiveInComment(kind, text[pos:end])
|
|
246
|
+
if matched == nil {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
if matched.directive == "nocheck" && firstStatementLine >= 0 &&
|
|
250
|
+
firstStatementLine <= shimscanner.GetECMALineOfPosition(ctx.File, pos) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
policy := options.policy(matched.directive)
|
|
255
|
+
if policy.ban {
|
|
256
|
+
if matched.directive == "ignore" {
|
|
257
|
+
reportTsIgnoreInsteadOfExpectError(ctx, text, pos, end)
|
|
258
|
+
} else {
|
|
259
|
+
ctx.ReportRange(pos, end, fmt.Sprintf(
|
|
260
|
+
"Do not use `@ts-%s` because it alters compilation errors.",
|
|
261
|
+
matched.directive,
|
|
262
|
+
))
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if policy.requireDescription {
|
|
266
|
+
if stringLength(strings.TrimSpace(matched.description)) < options.minimumDescriptionLength {
|
|
267
|
+
ctx.ReportRange(pos, end, fmt.Sprintf(
|
|
268
|
+
"Include a description after the `@ts-%[1]s` directive to explain why the @ts-%[1]s is necessary. The description must be %[2]d characters or longer.",
|
|
269
|
+
matched.directive, options.minimumDescriptionLength,
|
|
270
|
+
))
|
|
271
|
+
} else if policy.format != nil && !policy.format.MatchString(matched.description) {
|
|
272
|
+
ctx.ReportRange(pos, end, fmt.Sprintf(
|
|
273
|
+
"The description for the `@ts-%s` directive must match the %s format.",
|
|
274
|
+
matched.directive, policy.format.String(),
|
|
275
|
+
))
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// reportTsIgnoreInsteadOfExpectError reports a banned `@ts-ignore` with the
|
|
282
|
+
// upstream opt-in suggestion: rewrite the first `@ts-ignore` occurrence in
|
|
283
|
+
// the comment body to `@ts-expect-error`. This must not be an autofix because
|
|
284
|
+
// an error-free following line turns the rewrite into compiler error TS2578.
|
|
285
|
+
func reportTsIgnoreInsteadOfExpectError(ctx *Context, text string, pos, end int) {
|
|
286
|
+
const message = "Use `@ts-expect-error` instead of `@ts-ignore`, as `@ts-ignore` will do nothing if the following line is error-free."
|
|
287
|
+
// Search the comment body (both `//` and `/*` delimiters are two bytes),
|
|
288
|
+
// mirroring upstream's comment.value.replace(/@ts-ignore/, ...) which
|
|
289
|
+
// rewrites the first occurrence in the delimiter-free value.
|
|
290
|
+
body := text[pos:end]
|
|
291
|
+
index := strings.Index(body[2:], "@ts-ignore")
|
|
292
|
+
if index < 0 {
|
|
293
|
+
ctx.ReportRange(pos, end, message)
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
editPos := pos + 2 + index
|
|
297
|
+
ctx.ReportRangeSuggestion(
|
|
298
|
+
pos,
|
|
299
|
+
end,
|
|
300
|
+
message,
|
|
301
|
+
"Replace \"@ts-ignore\" with \"@ts-expect-error\".",
|
|
302
|
+
TextEdit{
|
|
303
|
+
Pos: editPos,
|
|
304
|
+
End: editPos + len("@ts-ignore"),
|
|
305
|
+
Text: "@ts-expect-error",
|
|
306
|
+
},
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
func init() {
|
|
311
|
+
Register(banTsComment{})
|
|
312
|
+
}
|
|
@@ -12,22 +12,11 @@ import (
|
|
|
12
12
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
type boundariesElementTypes struct{}
|
|
16
|
-
type boundariesExternal struct{}
|
|
17
|
-
type boundariesEntryPoint struct{}
|
|
18
|
-
type boundariesNoPrivate struct{}
|
|
19
|
-
type boundariesNoUnknown struct{}
|
|
20
|
-
|
|
21
|
-
// boundariesDependencies is a v1 stub for the upstream unified
|
|
22
|
-
// `boundaries/dependencies` rule, which replaces the legacy
|
|
23
|
-
// `element-types` / `entry-point` / `external` / `no-private` /
|
|
24
|
-
// `no-unknown` rules with a single direction-aware policy block.
|
|
25
|
-
//
|
|
26
|
-
// The native engine accepts the same `elements` + `rules` config shape so
|
|
27
|
-
// projects can claim the rule id today, but the full direction
|
|
28
|
-
// validation is deferred. Until then this rule registers, decodes
|
|
29
|
-
// options without crashing, and emits no diagnostics.
|
|
30
|
-
type boundariesDependencies struct{}
|
|
15
|
+
type boundariesElementTypes struct{ optionsRule }
|
|
16
|
+
type boundariesExternal struct{ optionsRule }
|
|
17
|
+
type boundariesEntryPoint struct{ optionsRule }
|
|
18
|
+
type boundariesNoPrivate struct{ optionsRule }
|
|
19
|
+
type boundariesNoUnknown struct{ optionsRule }
|
|
31
20
|
|
|
32
21
|
func (boundariesElementTypes) Name() string { return "boundaries/element-types" }
|
|
33
22
|
func (boundariesExternal) Name() string { return "boundaries/external" }
|
|
@@ -43,15 +32,6 @@ func (boundariesNoPrivate) Visits() []shimast.Kind { return []shimast.Kind{sh
|
|
|
43
32
|
func (boundariesNoUnknown) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
44
33
|
func (boundariesDependencies) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
45
34
|
|
|
46
|
-
// Check is intentionally a no-op for the v1 stub. The rule accepts the
|
|
47
|
-
// upstream config shape via the shared `boundariesOptions` decoder so
|
|
48
|
-
// downstream configs type-check and load, then exits without reporting
|
|
49
|
-
// findings. Replace this body when the unified direction validation
|
|
50
|
-
// lands.
|
|
51
|
-
func (boundariesDependencies) Check(ctx *Context, node *shimast.Node) {
|
|
52
|
-
_ = decodeBoundariesOptions(ctx)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
35
|
func (boundariesElementTypes) Check(ctx *Context, node *shimast.Node) {
|
|
56
36
|
opts := decodeBoundariesOptions(ctx)
|
|
57
37
|
if len(opts.Elements) == 0 {
|
|
@@ -236,9 +216,12 @@ type boundaryFile struct {
|
|
|
236
216
|
}
|
|
237
217
|
|
|
238
218
|
type boundaryDependency struct {
|
|
239
|
-
node
|
|
240
|
-
specifier
|
|
241
|
-
relative
|
|
219
|
+
node *shimast.Node
|
|
220
|
+
specifier string
|
|
221
|
+
relative bool
|
|
222
|
+
kind string
|
|
223
|
+
nodeKind string
|
|
224
|
+
specifiers []string
|
|
242
225
|
}
|
|
243
226
|
|
|
244
227
|
func decodeBoundariesOptions(ctx *Context) boundariesOptions {
|
|
@@ -250,7 +233,28 @@ func decodeBoundariesOptions(ctx *Context) boundariesOptions {
|
|
|
250
233
|
}
|
|
251
234
|
|
|
252
235
|
func collectBoundaryDependencies(node *shimast.Node) []boundaryDependency {
|
|
236
|
+
return collectBoundaryDependenciesWithSyntax(node, false)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func collectBoundaryPolicyDependencies(node *shimast.Node) []boundaryDependency {
|
|
240
|
+
return collectBoundaryDependenciesWithSyntax(node, true)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func collectBoundaryDependenciesWithSyntax(node *shimast.Node, extended bool) []boundaryDependency {
|
|
253
244
|
out := []boundaryDependency{}
|
|
245
|
+
appendDependency := func(sourceNode *shimast.Node, specifier, kind, nodeKind string, specifiers []string) {
|
|
246
|
+
if sourceNode == nil || specifier == "" {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
out = append(out, boundaryDependency{
|
|
250
|
+
node: sourceNode,
|
|
251
|
+
specifier: specifier,
|
|
252
|
+
relative: isRelativeBoundarySpecifier(specifier),
|
|
253
|
+
kind: kind,
|
|
254
|
+
nodeKind: nodeKind,
|
|
255
|
+
specifiers: specifiers,
|
|
256
|
+
})
|
|
257
|
+
}
|
|
254
258
|
var walk func(*shimast.Node)
|
|
255
259
|
walk = func(n *shimast.Node) {
|
|
256
260
|
if n == nil {
|
|
@@ -260,25 +264,85 @@ func collectBoundaryDependencies(node *shimast.Node) []boundaryDependency {
|
|
|
260
264
|
case shimast.KindImportDeclaration:
|
|
261
265
|
if imp := n.AsImportDeclaration(); imp != nil && imp.ModuleSpecifier != nil {
|
|
262
266
|
specifier := stringLiteralText(imp.ModuleSpecifier)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
names, typeOnly := noRestrictedImportsImportNames(imp)
|
|
268
|
+
appendDependency(
|
|
269
|
+
imp.ModuleSpecifier,
|
|
270
|
+
specifier,
|
|
271
|
+
boundaryDependencyKind(typeOnly, false),
|
|
272
|
+
"ImportDeclaration",
|
|
273
|
+
boundaryImportedNames(names),
|
|
274
|
+
)
|
|
270
275
|
}
|
|
271
276
|
case shimast.KindExportDeclaration:
|
|
272
277
|
if exp := n.AsExportDeclaration(); exp != nil && exp.ModuleSpecifier != nil {
|
|
273
278
|
specifier := stringLiteralText(exp.ModuleSpecifier)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
names, typeOnly := noRestrictedImportsExportNames(nil, n, exp)
|
|
280
|
+
appendDependency(
|
|
281
|
+
exp.ModuleSpecifier,
|
|
282
|
+
specifier,
|
|
283
|
+
boundaryDependencyKind(typeOnly, false),
|
|
284
|
+
"ExportDeclaration",
|
|
285
|
+
boundaryImportedNames(names),
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
case shimast.KindImportEqualsDeclaration:
|
|
289
|
+
if !extended {
|
|
290
|
+
break
|
|
291
|
+
}
|
|
292
|
+
declaration := n.AsImportEqualsDeclaration()
|
|
293
|
+
if declaration != nil && declaration.ModuleReference != nil &&
|
|
294
|
+
declaration.ModuleReference.Kind == shimast.KindExternalModuleReference {
|
|
295
|
+
module := declaration.ModuleReference.AsExternalModuleReference()
|
|
296
|
+
if module != nil && module.Expression != nil {
|
|
297
|
+
appendDependency(
|
|
298
|
+
module.Expression,
|
|
299
|
+
stringLiteralText(module.Expression),
|
|
300
|
+
boundaryDependencyKind(declaration.IsTypeOnly, false),
|
|
301
|
+
"ImportEqualsDeclaration",
|
|
302
|
+
nil,
|
|
303
|
+
)
|
|
280
304
|
}
|
|
281
305
|
}
|
|
306
|
+
case shimast.KindCallExpression:
|
|
307
|
+
if !extended {
|
|
308
|
+
break
|
|
309
|
+
}
|
|
310
|
+
call := n.AsCallExpression()
|
|
311
|
+
if call == nil || call.Expression == nil || call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
|
|
312
|
+
break
|
|
313
|
+
}
|
|
314
|
+
argument := call.Arguments.Nodes[0]
|
|
315
|
+
if argument == nil {
|
|
316
|
+
break
|
|
317
|
+
}
|
|
318
|
+
switch {
|
|
319
|
+
case call.Expression.Kind == shimast.KindImportKeyword:
|
|
320
|
+
appendDependency(argument, stringLiteralText(argument), "value", "ImportCall", nil)
|
|
321
|
+
case identifierText(call.Expression) == "require":
|
|
322
|
+
appendDependency(argument, stringLiteralText(argument), "value", "RequireCall", nil)
|
|
323
|
+
}
|
|
324
|
+
case shimast.KindImportType:
|
|
325
|
+
if !extended {
|
|
326
|
+
break
|
|
327
|
+
}
|
|
328
|
+
imported := n.AsImportTypeNode()
|
|
329
|
+
if imported == nil || imported.Argument == nil || imported.Argument.Kind != shimast.KindLiteralType {
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
literalType := imported.Argument.AsLiteralTypeNode()
|
|
333
|
+
if literalType == nil || literalType.Literal == nil {
|
|
334
|
+
break
|
|
335
|
+
}
|
|
336
|
+
appendDependency(
|
|
337
|
+
literalType.Literal,
|
|
338
|
+
stringLiteralText(literalType.Literal),
|
|
339
|
+
// An `import("m")` node in type position resolves types only, so it
|
|
340
|
+
// is a `type` dependency; the `typeof import("m")` flavor queries the
|
|
341
|
+
// module's value shape and keeps the dedicated `typeof` kind.
|
|
342
|
+
boundaryDependencyKind(true, imported.IsTypeOf),
|
|
343
|
+
"ImportType",
|
|
344
|
+
nil,
|
|
345
|
+
)
|
|
282
346
|
}
|
|
283
347
|
n.ForEachChild(func(child *shimast.Node) bool {
|
|
284
348
|
walk(child)
|
|
@@ -289,6 +353,26 @@ func collectBoundaryDependencies(node *shimast.Node) []boundaryDependency {
|
|
|
289
353
|
return out
|
|
290
354
|
}
|
|
291
355
|
|
|
356
|
+
func boundaryDependencyKind(typeOnly, typeOf bool) string {
|
|
357
|
+
if typeOf {
|
|
358
|
+
return "typeof"
|
|
359
|
+
}
|
|
360
|
+
if typeOnly {
|
|
361
|
+
return "type"
|
|
362
|
+
}
|
|
363
|
+
return "value"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
func boundaryImportedNames(names []noRestrictedImportsImportedName) []string {
|
|
367
|
+
out := make([]string, 0, len(names))
|
|
368
|
+
for _, name := range names {
|
|
369
|
+
if name.name != "" {
|
|
370
|
+
out = append(out, name.name)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return uniqueBoundaryStrings(out)
|
|
374
|
+
}
|
|
375
|
+
|
|
292
376
|
func classifyBoundaryFile(fileName string, elements []boundaryElement) *boundaryFile {
|
|
293
377
|
normalized := normalizeBoundaryPath(fileName)
|
|
294
378
|
matches := []boundaryFile{}
|
|
@@ -357,10 +441,10 @@ func resolveBoundaryImport(sourceFileName, specifier string) (string, bool) {
|
|
|
357
441
|
target := filepath.Clean(filepath.Join(base, filepath.FromSlash(specifier)))
|
|
358
442
|
candidates := []string{target}
|
|
359
443
|
if filepath.Ext(target) == "" {
|
|
360
|
-
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
444
|
+
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts", ".d.ts", ".d.mts", ".d.cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
361
445
|
candidates = append(candidates, target+ext)
|
|
362
446
|
}
|
|
363
|
-
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
447
|
+
for _, ext := range []string{".ts", ".tsx", ".mts", ".cts", ".d.ts", ".d.mts", ".d.cts", ".js", ".jsx", ".mjs", ".cjs"} {
|
|
364
448
|
candidates = append(candidates, filepath.Join(target, "index"+ext))
|
|
365
449
|
}
|
|
366
450
|
}
|
|
@@ -499,6 +583,7 @@ func boundaryDisplayPath(path string) string {
|
|
|
499
583
|
}
|
|
500
584
|
|
|
501
585
|
func normalizeBoundaryPath(path string) string {
|
|
586
|
+
path = strings.ReplaceAll(path, `\`, "/")
|
|
502
587
|
return filepath.ToSlash(filepath.Clean(path))
|
|
503
588
|
}
|
|
504
589
|
|