@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,222 @@
|
|
|
1
|
+
// Grapheme-cluster string length, mirroring typescript-eslint's
|
|
2
|
+
// `getStringLength` utility. Directive descriptions are measured as Unicode
|
|
3
|
+
// extended grapheme clusters, the same units exposed by `Intl.Segmenter` with
|
|
4
|
+
// `granularity: "grapheme"`, rather than bytes, UTF-16 code units, or runes.
|
|
5
|
+
//
|
|
6
|
+
// The implementation follows the ordered extended-cluster rules in UAX #29.
|
|
7
|
+
// Its generated property tables are pinned to Unicode 16.0.0, the Unicode
|
|
8
|
+
// version used by the Node 24 runtime in this repository's CI. Regenerate the
|
|
9
|
+
// tables and official conformance corpus together when that runtime advances.
|
|
10
|
+
package linthost
|
|
11
|
+
|
|
12
|
+
//go:generate go run ../tools/graphemegen -root ..
|
|
13
|
+
|
|
14
|
+
// stringLength returns the number of Unicode extended grapheme clusters in s.
|
|
15
|
+
func stringLength(s string) int {
|
|
16
|
+
return graphemeCount(s)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// graphemeCount applies UAX #29's ordered boundary rules in one pass. The
|
|
20
|
+
// segmenter carries only the left contexts required by GB9c, GB11, and
|
|
21
|
+
// GB12/GB13, keeping long combining and regional-indicator runs linear.
|
|
22
|
+
func graphemeCount(s string) int {
|
|
23
|
+
count := 0
|
|
24
|
+
var segmenter graphemeSegmenter
|
|
25
|
+
for _, current := range s {
|
|
26
|
+
properties := graphemeProperties(current)
|
|
27
|
+
if segmenter.hasBoundaryBefore(properties) {
|
|
28
|
+
count++
|
|
29
|
+
}
|
|
30
|
+
segmenter.consume(properties)
|
|
31
|
+
}
|
|
32
|
+
return count
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type graphemeRuneProperties struct {
|
|
36
|
+
breakClass graphemeBreakClass
|
|
37
|
+
indicConjunctClass indicConjunctBreakClass
|
|
38
|
+
extendedPictographic bool
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type indicConjunctState uint8
|
|
42
|
+
|
|
43
|
+
const (
|
|
44
|
+
indicConjunctStateNone indicConjunctState = iota
|
|
45
|
+
indicConjunctStateConsonant
|
|
46
|
+
indicConjunctStateLinked
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
type emojiSequenceState uint8
|
|
50
|
+
|
|
51
|
+
const (
|
|
52
|
+
emojiSequenceStateNone emojiSequenceState = iota
|
|
53
|
+
emojiSequenceStatePictographic
|
|
54
|
+
emojiSequenceStateZWJ
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
type graphemeSegmenter struct {
|
|
58
|
+
hasPrevious bool
|
|
59
|
+
previous graphemeRuneProperties
|
|
60
|
+
regionalIndicators int
|
|
61
|
+
indicConjunct indicConjunctState
|
|
62
|
+
emojiSequence emojiSequenceState
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// hasBoundaryBefore applies GB3 through GB999 in normative order. Start of
|
|
66
|
+
// text is always a boundary (GB1); end of text needs no explicit handling when
|
|
67
|
+
// only the number of clusters is required.
|
|
68
|
+
func (s *graphemeSegmenter) hasBoundaryBefore(current graphemeRuneProperties) bool {
|
|
69
|
+
if !s.hasPrevious {
|
|
70
|
+
return true
|
|
71
|
+
}
|
|
72
|
+
previous := s.previous.breakClass
|
|
73
|
+
next := current.breakClass
|
|
74
|
+
|
|
75
|
+
// GB3: keep CRLF together.
|
|
76
|
+
if previous == graphemeBreakCR && next == graphemeBreakLF {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
// GB4/GB5: otherwise break before and after controls.
|
|
80
|
+
if isGraphemeControlClass(previous) || isGraphemeControlClass(next) {
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
// GB6-GB8: keep Hangul and other data-defined conjoining sequences.
|
|
84
|
+
if isConjoiningGraphemeJoin(previous, next) {
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
// GB9/GB9a: extending characters, ZWJ, and spacing marks continue the
|
|
88
|
+
// current cluster.
|
|
89
|
+
if next == graphemeBreakExtend || next == graphemeBreakZWJ || next == graphemeBreakSpacingMark {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
// GB9b: Prepend joins the following non-control character.
|
|
93
|
+
if previous == graphemeBreakPrepend {
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
// GB9c: an Indic consonant-linker sequence joins its next consonant.
|
|
97
|
+
if current.indicConjunctClass == indicConjunctBreakConsonant && s.indicConjunct == indicConjunctStateLinked {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
// GB11: Extended_Pictographic Extend* ZWJ joins the next pictograph.
|
|
101
|
+
if current.extendedPictographic && s.emojiSequence == emojiSequenceStateZWJ {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
// GB12/GB13: regional indicators join in pairs. The state is the
|
|
105
|
+
// uninterrupted RI count immediately before this boundary.
|
|
106
|
+
if next == graphemeBreakRegionalIndicator && s.regionalIndicators%2 == 1 {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
// GB999: all remaining positions are boundaries.
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func (s *graphemeSegmenter) consume(current graphemeRuneProperties) {
|
|
114
|
+
if current.breakClass == graphemeBreakRegionalIndicator {
|
|
115
|
+
s.regionalIndicators++
|
|
116
|
+
} else {
|
|
117
|
+
s.regionalIndicators = 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
switch current.indicConjunctClass {
|
|
121
|
+
case indicConjunctBreakConsonant:
|
|
122
|
+
s.indicConjunct = indicConjunctStateConsonant
|
|
123
|
+
case indicConjunctBreakExtend:
|
|
124
|
+
// Extend preserves a preceding consonant/linker context.
|
|
125
|
+
case indicConjunctBreakLinker:
|
|
126
|
+
if s.indicConjunct != indicConjunctStateNone {
|
|
127
|
+
s.indicConjunct = indicConjunctStateLinked
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
s.indicConjunct = indicConjunctStateNone
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
switch {
|
|
134
|
+
case current.extendedPictographic:
|
|
135
|
+
s.emojiSequence = emojiSequenceStatePictographic
|
|
136
|
+
case current.breakClass == graphemeBreakExtend && s.emojiSequence == emojiSequenceStatePictographic:
|
|
137
|
+
// Extend preserves the pictograph context immediately before a ZWJ.
|
|
138
|
+
case current.breakClass == graphemeBreakZWJ && s.emojiSequence == emojiSequenceStatePictographic:
|
|
139
|
+
s.emojiSequence = emojiSequenceStateZWJ
|
|
140
|
+
default:
|
|
141
|
+
s.emojiSequence = emojiSequenceStateNone
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
s.previous = current
|
|
145
|
+
s.hasPrevious = true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
func graphemeProperties(r rune) graphemeRuneProperties {
|
|
149
|
+
return graphemeRuneProperties{
|
|
150
|
+
breakClass: lookupGraphemeBreakClass(r),
|
|
151
|
+
indicConjunctClass: lookupIndicConjunctBreakClass(r),
|
|
152
|
+
extendedPictographic: isExtendedPictographic(r),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func lookupGraphemeBreakClass(r rune) graphemeBreakClass {
|
|
157
|
+
lo, hi := 0, len(graphemeBreakRanges)
|
|
158
|
+
for lo < hi {
|
|
159
|
+
middle := int(uint(lo+hi) >> 1)
|
|
160
|
+
candidate := graphemeBreakRanges[middle]
|
|
161
|
+
switch {
|
|
162
|
+
case r < candidate.lo:
|
|
163
|
+
hi = middle
|
|
164
|
+
case r > candidate.hi:
|
|
165
|
+
lo = middle + 1
|
|
166
|
+
default:
|
|
167
|
+
return candidate.class
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return graphemeBreakOther
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func lookupIndicConjunctBreakClass(r rune) indicConjunctBreakClass {
|
|
174
|
+
lo, hi := 0, len(indicConjunctBreakRanges)
|
|
175
|
+
for lo < hi {
|
|
176
|
+
middle := int(uint(lo+hi) >> 1)
|
|
177
|
+
candidate := indicConjunctBreakRanges[middle]
|
|
178
|
+
switch {
|
|
179
|
+
case r < candidate.lo:
|
|
180
|
+
hi = middle
|
|
181
|
+
case r > candidate.hi:
|
|
182
|
+
lo = middle + 1
|
|
183
|
+
default:
|
|
184
|
+
return candidate.class
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return indicConjunctBreakNone
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
func isExtendedPictographic(r rune) bool {
|
|
191
|
+
lo, hi := 0, len(extendedPictographicRanges)
|
|
192
|
+
for lo < hi {
|
|
193
|
+
middle := int(uint(lo+hi) >> 1)
|
|
194
|
+
candidate := extendedPictographicRanges[middle]
|
|
195
|
+
switch {
|
|
196
|
+
case r < candidate.lo:
|
|
197
|
+
hi = middle
|
|
198
|
+
case r > candidate.hi:
|
|
199
|
+
lo = middle + 1
|
|
200
|
+
default:
|
|
201
|
+
return true
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
func isGraphemeControlClass(class graphemeBreakClass) bool {
|
|
208
|
+
return class == graphemeBreakControl || class == graphemeBreakCR || class == graphemeBreakLF
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func isConjoiningGraphemeJoin(previous, next graphemeBreakClass) bool {
|
|
212
|
+
switch previous {
|
|
213
|
+
case graphemeBreakL:
|
|
214
|
+
return next == graphemeBreakL || next == graphemeBreakV || next == graphemeBreakLV || next == graphemeBreakLVT
|
|
215
|
+
case graphemeBreakLV, graphemeBreakV:
|
|
216
|
+
return next == graphemeBreakV || next == graphemeBreakT
|
|
217
|
+
case graphemeBreakLVT, graphemeBreakT:
|
|
218
|
+
return next == graphemeBreakT
|
|
219
|
+
default:
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Reference ttsc plugin: ESLint-style lint rules
|
|
3
|
+
"version": "0.19.1",
|
|
4
|
+
"description": "Reference ttsc plugin: ESLint-style lint rules over the TypeScript-Go Program used by the type-check pass.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"README.md",
|
|
28
28
|
"go.mod",
|
|
29
|
+
"internal",
|
|
29
30
|
"lib",
|
|
30
31
|
"linthost",
|
|
31
32
|
"plugin",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"@types/node": "^25.3.0",
|
|
37
38
|
"rimraf": "^6.1.2",
|
|
38
39
|
"typescript": "^7.0.2",
|
|
39
|
-
"ttsc": "0.
|
|
40
|
+
"ttsc": "0.19.1"
|
|
40
41
|
},
|
|
41
42
|
"repository": {
|
|
42
43
|
"type": "git",
|
package/rule/project.go
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
package rule
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
|
|
6
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
7
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// ProjectIdentity names one loaded TypeScript Program without conflating the
|
|
11
|
+
// caller's path spelling with the filesystem identity used by the compiler.
|
|
12
|
+
// Empty explicit fields mean the caller did not provide that channel.
|
|
13
|
+
type ProjectIdentity struct {
|
|
14
|
+
LifecycleID string `json:"lifecycleId"`
|
|
15
|
+
InvocationCwd string `json:"invocationCwd"`
|
|
16
|
+
LogicalConfigPath string `json:"logicalConfigPath"`
|
|
17
|
+
LogicalProjectRoot string `json:"logicalProjectRoot"`
|
|
18
|
+
PhysicalConfigPath string `json:"physicalConfigPath"`
|
|
19
|
+
PhysicalProjectRoot string `json:"physicalProjectRoot"`
|
|
20
|
+
ExplicitProjectRoot string `json:"explicitProjectRoot,omitempty"`
|
|
21
|
+
PluginConfigOrigin string `json:"pluginConfigOrigin,omitempty"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ProjectRuleStatus describes whether a named project rule exists, was
|
|
25
|
+
// configured, and completed during the current Program cycle.
|
|
26
|
+
type ProjectRuleStatus string
|
|
27
|
+
|
|
28
|
+
const (
|
|
29
|
+
ProjectRuleAbsent ProjectRuleStatus = "absent"
|
|
30
|
+
ProjectRuleOff ProjectRuleStatus = "off"
|
|
31
|
+
ProjectRuleNotEvaluated ProjectRuleStatus = "not_evaluated"
|
|
32
|
+
ProjectRulePassed ProjectRuleStatus = "passed"
|
|
33
|
+
ProjectRuleFailed ProjectRuleStatus = "failed"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// ProjectFinding is a non-file finding retained in a project rule's cycle
|
|
37
|
+
// result. Project findings never contain edits or source ranges.
|
|
38
|
+
type ProjectFinding struct {
|
|
39
|
+
Message string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ProjectRuleResult is one snapshot of a named project rule in the current
|
|
43
|
+
// Program cycle. State is the contributor-owned value attached during the
|
|
44
|
+
// project check; the host neither interprets nor synchronizes its contents.
|
|
45
|
+
// Findings is returned as a defensive copy by host result readers.
|
|
46
|
+
//
|
|
47
|
+
// Evaluated results retain a cycle-scoped failure channel through file-rule
|
|
48
|
+
// dispatch. Call Report or Fail immediately before a guarded operation, then
|
|
49
|
+
// call Context.ProjectResult again when the updated status is needed. Absent,
|
|
50
|
+
// off, and not-evaluated results have no state or live failure channel.
|
|
51
|
+
type ProjectRuleResult struct {
|
|
52
|
+
Status ProjectRuleStatus
|
|
53
|
+
State any
|
|
54
|
+
Findings []ProjectFinding
|
|
55
|
+
|
|
56
|
+
reporter ProjectReporter
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// NewProjectRuleResult constructs one host-owned project-result snapshot.
|
|
60
|
+
// Contributor code normally receives this value from Context.ProjectResult
|
|
61
|
+
// and does not construct it.
|
|
62
|
+
func NewProjectRuleResult(
|
|
63
|
+
status ProjectRuleStatus,
|
|
64
|
+
state any,
|
|
65
|
+
findings []ProjectFinding,
|
|
66
|
+
reporter ProjectReporter,
|
|
67
|
+
) ProjectRuleResult {
|
|
68
|
+
return ProjectRuleResult{
|
|
69
|
+
Status: status,
|
|
70
|
+
State: state,
|
|
71
|
+
Findings: append([]ProjectFinding(nil), findings...),
|
|
72
|
+
reporter: reporter,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fail marks this evaluated project result failed without adding a finding.
|
|
77
|
+
// It is a no-op after file dispatch or for a result that was not evaluated.
|
|
78
|
+
func (r ProjectRuleResult) Fail() {
|
|
79
|
+
if r.reporter != nil {
|
|
80
|
+
r.reporter.Fail()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Report records one project finding and marks this evaluated result failed.
|
|
85
|
+
// Equal messages are deduplicated by the host. It is a no-op after file
|
|
86
|
+
// dispatch or for a result that was not evaluated.
|
|
87
|
+
func (r ProjectRuleResult) Report(message string) {
|
|
88
|
+
if r.reporter != nil {
|
|
89
|
+
r.reporter.Report(message)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ProjectResultReader supplies live project state to later file-rule contexts.
|
|
94
|
+
// Hosts return ProjectRuleAbsent for names with no registered project rule.
|
|
95
|
+
type ProjectResultReader interface {
|
|
96
|
+
ProjectResult(name string) ProjectRuleResult
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ProjectRule is a contributor check that runs once for a loaded Program
|
|
100
|
+
// before any node rule dispatch. It has no AST visit list or synthetic file.
|
|
101
|
+
type ProjectRule interface {
|
|
102
|
+
Name() string
|
|
103
|
+
Check(ctx *ProjectContext)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ProjectReporter is the cycle-scoped failure channel available to project
|
|
107
|
+
// helpers. Report records a deterministic project finding and also marks the
|
|
108
|
+
// current rule failed; Fail marks failure without adding a finding.
|
|
109
|
+
type ProjectReporter interface {
|
|
110
|
+
Fail()
|
|
111
|
+
Report(message string)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ProjectContext contains the immutable inputs for one project-rule check.
|
|
115
|
+
// Sources is a defensive copy of the Program's tsconfig-selected user sources.
|
|
116
|
+
type ProjectContext struct {
|
|
117
|
+
Identity ProjectIdentity
|
|
118
|
+
Sources []*shimast.SourceFile
|
|
119
|
+
Checker *shimchecker.Checker
|
|
120
|
+
Severity Severity
|
|
121
|
+
Options json.RawMessage
|
|
122
|
+
|
|
123
|
+
reporter ProjectReporter
|
|
124
|
+
stateSetter projectStateSetter
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type projectStateSetter interface {
|
|
128
|
+
SetState(state any)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// NewProjectContext constructs the context a host passes to ProjectRule.Check.
|
|
132
|
+
// Contributor code normally receives this value and does not construct it.
|
|
133
|
+
func NewProjectContext(
|
|
134
|
+
identity ProjectIdentity,
|
|
135
|
+
sources []*shimast.SourceFile,
|
|
136
|
+
checker *shimchecker.Checker,
|
|
137
|
+
severity Severity,
|
|
138
|
+
options json.RawMessage,
|
|
139
|
+
reporter ProjectReporter,
|
|
140
|
+
) *ProjectContext {
|
|
141
|
+
copiedSources := append([]*shimast.SourceFile(nil), sources...)
|
|
142
|
+
stateSetter, _ := reporter.(projectStateSetter)
|
|
143
|
+
return &ProjectContext{
|
|
144
|
+
Identity: identity,
|
|
145
|
+
Sources: copiedSources,
|
|
146
|
+
Checker: checker,
|
|
147
|
+
Severity: severity,
|
|
148
|
+
Options: append(json.RawMessage(nil), options...),
|
|
149
|
+
reporter: reporter,
|
|
150
|
+
stateSetter: stateSetter,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// DecodeOptions unmarshals the configured project-rule options into out. A
|
|
155
|
+
// missing options tuple leaves out unchanged and returns nil.
|
|
156
|
+
func (c *ProjectContext) DecodeOptions(out interface{}) error {
|
|
157
|
+
if c == nil || len(c.Options) == 0 {
|
|
158
|
+
return nil
|
|
159
|
+
}
|
|
160
|
+
return json.Unmarshal(c.Options, out)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// SetState attaches one contributor-owned value to this rule's evaluated
|
|
164
|
+
// result. The exact value is returned to file rules in the same Program cycle;
|
|
165
|
+
// contributors own any synchronization needed inside it. The host does not
|
|
166
|
+
// serialize the value or retain it for a later watch or LSP rebuild.
|
|
167
|
+
func (c *ProjectContext) SetState(state any) {
|
|
168
|
+
if c == nil || c.stateSetter == nil || c.Severity == SeverityOff {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
c.stateSetter.SetState(state)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Fail marks the current project rule failed without adding a diagnostic.
|
|
175
|
+
func (c *ProjectContext) Fail() {
|
|
176
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
c.reporter.Fail()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Report records one non-file project finding and marks the rule failed.
|
|
183
|
+
func (c *ProjectContext) Report(message string) {
|
|
184
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
c.reporter.Report(message)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var projectRegistry []ProjectRule
|
|
191
|
+
|
|
192
|
+
// RegisterProject adds a contributor project rule to the global registry.
|
|
193
|
+
// Hosts validate duplicate names after all contributor init functions finish.
|
|
194
|
+
func RegisterProject(r ProjectRule) {
|
|
195
|
+
if r == nil {
|
|
196
|
+
panic("rule: RegisterProject called with nil rule")
|
|
197
|
+
}
|
|
198
|
+
projectRegistry = append(projectRegistry, r)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// RegisteredProjects returns a defensive copy of all registered project rules.
|
|
202
|
+
func RegisteredProjects() []ProjectRule {
|
|
203
|
+
out := make([]ProjectRule, len(projectRegistry))
|
|
204
|
+
copy(out, projectRegistry)
|
|
205
|
+
return out
|
|
206
|
+
}
|
package/rule/rule.go
CHANGED
|
@@ -112,6 +112,39 @@ type DeclarationFileRule interface {
|
|
|
112
112
|
VisitsDeclarationFiles() bool
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// TypeAwareRule is an optional marker contributors implement to declare
|
|
116
|
+
// whether their rule reads `Context.Checker`. The host cannot infer a
|
|
117
|
+
// third-party rule's shape, so a contributor that does not implement this
|
|
118
|
+
// marker keeps the conservative default: it is treated as type-aware and
|
|
119
|
+
// receives a live checker.
|
|
120
|
+
//
|
|
121
|
+
// Being treated as type-aware is not free. The host creates a standalone
|
|
122
|
+
// checker spanning every source file, and the engine walks files serially so
|
|
123
|
+
// that checker is never accessed concurrently. A purely syntactic rule that
|
|
124
|
+
// never touches `Context.Checker` pays both costs for nothing.
|
|
125
|
+
//
|
|
126
|
+
// A contributor whose rule is AST-only can implement this with
|
|
127
|
+
// `NeedsTypeChecker() bool { return false }` to opt out of the checker path,
|
|
128
|
+
// preserving the engine's parallel file walk. Returning `true` is equivalent
|
|
129
|
+
// to not implementing the interface at all. A rule that returns `false` must
|
|
130
|
+
// not read `Context.Checker`: the host is free to leave it nil.
|
|
131
|
+
type TypeAwareRule interface {
|
|
132
|
+
Rule
|
|
133
|
+
NeedsTypeChecker() bool
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// OptionsRule is an optional marker contributors implement to declare whether
|
|
137
|
+
// their rule accepts an options slot in its `[severity, options]` setting.
|
|
138
|
+
// Contributor rules default to accepting options for backward compatibility
|
|
139
|
+
// with the original public Context.Options contract. Return false for a
|
|
140
|
+
// genuinely optionless rule so the host can reject accidental payloads before
|
|
141
|
+
// linting. The domain-specific method name prevents an unrelated generic
|
|
142
|
+
// AcceptsOptions method on an existing contributor from opting in by accident.
|
|
143
|
+
// ProjectRule implementations may use the same marker.
|
|
144
|
+
type OptionsRule interface {
|
|
145
|
+
AcceptsTtscLintOptions() bool
|
|
146
|
+
}
|
|
147
|
+
|
|
115
148
|
// Reporter is the engine-supplied callback that records a finding. The
|
|
116
149
|
// host implements this and passes it to `NewContext` when invoking a
|
|
117
150
|
// contributor rule.
|
|
@@ -191,6 +224,7 @@ type Context struct {
|
|
|
191
224
|
Options json.RawMessage
|
|
192
225
|
|
|
193
226
|
reporter Reporter
|
|
227
|
+
results ProjectResultReader
|
|
194
228
|
}
|
|
195
229
|
|
|
196
230
|
// NewContext constructs a Context for the engine to pass into a
|
|
@@ -202,14 +236,37 @@ func NewContext(
|
|
|
202
236
|
severity Severity,
|
|
203
237
|
options json.RawMessage,
|
|
204
238
|
reporter Reporter,
|
|
239
|
+
) *Context {
|
|
240
|
+
return NewContextWithProjectResults(file, checker, severity, options, reporter, nil)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// NewContextWithProjectResults constructs a file-rule Context with the live
|
|
244
|
+
// project results for the same loaded Program cycle.
|
|
245
|
+
func NewContextWithProjectResults(
|
|
246
|
+
file *shimast.SourceFile,
|
|
247
|
+
checker *shimchecker.Checker,
|
|
248
|
+
severity Severity,
|
|
249
|
+
options json.RawMessage,
|
|
250
|
+
reporter Reporter,
|
|
251
|
+
results ProjectResultReader,
|
|
205
252
|
) *Context {
|
|
206
253
|
return &Context{
|
|
207
254
|
File: file,
|
|
208
255
|
Checker: checker,
|
|
209
256
|
Severity: severity,
|
|
210
|
-
Options: options,
|
|
257
|
+
Options: append(json.RawMessage(nil), options...),
|
|
211
258
|
reporter: reporter,
|
|
259
|
+
results: results,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ProjectResult returns a current snapshot for a named project rule in this
|
|
264
|
+
// file's Program cycle. Missing registrations return ProjectRuleAbsent.
|
|
265
|
+
func (c *Context) ProjectResult(name string) ProjectRuleResult {
|
|
266
|
+
if c == nil || c.results == nil {
|
|
267
|
+
return ProjectRuleResult{Status: ProjectRuleAbsent}
|
|
212
268
|
}
|
|
269
|
+
return c.results.ProjectResult(name)
|
|
213
270
|
}
|
|
214
271
|
|
|
215
272
|
// DecodeOptions unmarshals the rule's options blob into `out`. Returns
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ type TtscPluginDescriptor = {
|
|
|
22
22
|
capabilities?: {
|
|
23
23
|
diagnosticsTiming?: boolean;
|
|
24
24
|
lsp?: boolean;
|
|
25
|
+
projectContextArgs?: boolean;
|
|
25
26
|
threadingArgs?: boolean;
|
|
26
27
|
};
|
|
27
28
|
contributors?: TtscPluginContributor[];
|
|
@@ -43,6 +44,12 @@ type TtscPluginFactoryContext<TConfig> = {
|
|
|
43
44
|
/** This descriptor's own path — the ESM-safe replacement for `__filename`. */
|
|
44
45
|
filename: string;
|
|
45
46
|
plugin: TConfig;
|
|
47
|
+
/**
|
|
48
|
+
* Caller-declared anchor for plugin config-file discovery, present when the
|
|
49
|
+
* embedder compiles through a generated tsconfig outside the project (see
|
|
50
|
+
* `ITtscPluginFactoryContext.pluginConfigDir`).
|
|
51
|
+
*/
|
|
52
|
+
pluginConfigDir?: string;
|
|
46
53
|
projectRoot: string;
|
|
47
54
|
tsconfig: string;
|
|
48
55
|
};
|
|
@@ -104,7 +111,9 @@ const FRAMEWORK_KEYS = new Set<string>([
|
|
|
104
111
|
* (`lint.config.{ts,cts,mts,js,cjs,mjs,json}` or `ttsc-lint.config.*`). The
|
|
105
112
|
* tsconfig plugin entry carries no rule or plugin surface — it optionally names
|
|
106
113
|
* the config file via `configFile`, otherwise the file is discovered by walking
|
|
107
|
-
* upward from the tsconfig directory
|
|
114
|
+
* upward from the tsconfig directory (or from the caller-declared
|
|
115
|
+
* `pluginConfigDir` when the resolved tsconfig is a generated wrapper in a temp
|
|
116
|
+
* directory).
|
|
108
117
|
*
|
|
109
118
|
* The factory locates the config file, evaluates it (via ttsx for TS / ESM
|
|
110
119
|
* sources, `require` for CommonJS, `JSON.parse` for JSON), reads every
|
|
@@ -121,7 +130,12 @@ export default function createTtscPlugin(
|
|
|
121
130
|
// declared, so consumers (and the existing key-shape regression
|
|
122
131
|
// tests) see the same surface as before this feature shipped.
|
|
123
132
|
const descriptor: TtscPluginDescriptor = {
|
|
124
|
-
capabilities: {
|
|
133
|
+
capabilities: {
|
|
134
|
+
diagnosticsTiming: true,
|
|
135
|
+
lsp: true,
|
|
136
|
+
projectContextArgs: true,
|
|
137
|
+
threadingArgs: true,
|
|
138
|
+
},
|
|
125
139
|
name: "@ttsc/lint",
|
|
126
140
|
reportsTypeScriptDiagnostics: true,
|
|
127
141
|
// `context.dirname` is this descriptor's own directory in every load mode —
|
|
@@ -187,9 +201,12 @@ type ConfigPluginEntry = { namespace: string; source: string };
|
|
|
187
201
|
* Resolves the contributor lint plugins declared in the project's lint config
|
|
188
202
|
* file.
|
|
189
203
|
*
|
|
190
|
-
* - When the tsconfig plugin entry sets `configFile`, that exact file is loaded
|
|
204
|
+
* - When the tsconfig plugin entry sets `configFile`, that exact file is loaded
|
|
205
|
+
* (relative paths resolve against the tsconfig directory, or against the
|
|
206
|
+
* caller-declared `pluginConfigDir` when present).
|
|
191
207
|
* - Otherwise a `lint.config.*` / `ttsc-lint.config.*` file is discovered by
|
|
192
|
-
* walking upward from the tsconfig directory
|
|
208
|
+
* walking upward from the tsconfig directory (or from `pluginConfigDir` when
|
|
209
|
+
* present).
|
|
193
210
|
*
|
|
194
211
|
* Returns an empty array when no config file is set or discovered — the Go
|
|
195
212
|
* sidecar surfaces the missing-config error; the factory only needs to forward
|
|
@@ -201,7 +218,7 @@ function resolveConfigFileContributors(
|
|
|
201
218
|
const configFile = readConfigFileOption(context);
|
|
202
219
|
const configPath =
|
|
203
220
|
configFile !== undefined
|
|
204
|
-
? path.resolve(
|
|
221
|
+
? path.resolve(pluginConfigBaseDir(context), configFile)
|
|
205
222
|
: findLintConfigFile(context);
|
|
206
223
|
if (!configPath || !fs.existsSync(configPath)) return [];
|
|
207
224
|
|
|
@@ -260,18 +277,15 @@ function readConfigFileOption(
|
|
|
260
277
|
function findLintConfigFile(
|
|
261
278
|
context: TtscPluginFactoryContext<ITtscLintPluginConfig>,
|
|
262
279
|
): string | undefined {
|
|
263
|
-
// Mirror the Go
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
for (const origin of tsconfigDir === cwd
|
|
273
|
-
? [tsconfigDir]
|
|
274
|
-
: [tsconfigDir, cwd]) {
|
|
280
|
+
// Mirror the Go side (driver.PluginConfigBaseDir): the caller-declared
|
|
281
|
+
// pluginConfigDir is the single walk origin when present — it names the
|
|
282
|
+
// real project when the resolved tsconfig is a generated wrapper in a temp
|
|
283
|
+
// dir (@ttsc/unplugin's alias overlay), and it keeps the wrapper's temp
|
|
284
|
+
// ancestry out of the walk so a stray config planted there is never
|
|
285
|
+
// honored. Otherwise walk upward from the tsconfig directory first, then
|
|
286
|
+
// fall back to the working directory: that covers callers that point at an
|
|
287
|
+
// out-of-tree tsconfig without declaring an anchor.
|
|
288
|
+
for (const origin of discoveryConfigBaseDirs(context)) {
|
|
275
289
|
const discovered = findLintConfigFileFrom(origin);
|
|
276
290
|
if (discovered !== undefined) {
|
|
277
291
|
return discovered;
|
|
@@ -280,6 +294,17 @@ function findLintConfigFile(
|
|
|
280
294
|
return undefined;
|
|
281
295
|
}
|
|
282
296
|
|
|
297
|
+
function discoveryConfigBaseDirs(
|
|
298
|
+
context: TtscPluginFactoryContext<ITtscLintPluginConfig>,
|
|
299
|
+
): string[] {
|
|
300
|
+
if (context.pluginConfigDir) {
|
|
301
|
+
return [path.resolve(context.cwd ?? ".", context.pluginConfigDir)];
|
|
302
|
+
}
|
|
303
|
+
const tsconfigDir = tsconfigBaseDir(context);
|
|
304
|
+
const cwd = path.resolve(context.cwd ?? context.projectRoot);
|
|
305
|
+
return tsconfigDir === cwd ? [tsconfigDir] : [tsconfigDir, cwd];
|
|
306
|
+
}
|
|
307
|
+
|
|
283
308
|
function findLintConfigFileFrom(origin: string): string | undefined {
|
|
284
309
|
// Mirror the Go-side discovery loop: walk from `origin` upward, returning
|
|
285
310
|
// the first directory that has exactly one of the candidate filenames.
|
|
@@ -317,6 +342,22 @@ function findLintConfigFileFrom(origin: string): string | undefined {
|
|
|
317
342
|
}
|
|
318
343
|
}
|
|
319
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Base directory for resolving a relative `configFile` from the tsconfig plugin
|
|
347
|
+
* entry. Mirrors the Go side (driver.PluginConfigBaseDir): the caller-declared
|
|
348
|
+
* pluginConfigDir wins when present — the resolved tsconfig is then a generated
|
|
349
|
+
* wrapper in a temp directory that no longer identifies the project — otherwise
|
|
350
|
+
* the tsconfig directory, falling back to the working directory.
|
|
351
|
+
*/
|
|
352
|
+
function pluginConfigBaseDir(
|
|
353
|
+
context: TtscPluginFactoryContext<ITtscLintPluginConfig>,
|
|
354
|
+
): string {
|
|
355
|
+
if (context.pluginConfigDir) {
|
|
356
|
+
return path.resolve(context.cwd ?? ".", context.pluginConfigDir);
|
|
357
|
+
}
|
|
358
|
+
return tsconfigBaseDir(context);
|
|
359
|
+
}
|
|
360
|
+
|
|
320
361
|
function tsconfigBaseDir(
|
|
321
362
|
context: TtscPluginFactoryContext<ITtscLintPluginConfig>,
|
|
322
363
|
): string {
|
|
@@ -38,6 +38,9 @@ export interface ITtscLintConfig {
|
|
|
38
38
|
* Built-in rules are concrete interface properties for autocomplete and typo
|
|
39
39
|
* checking. Namespaced families and contributor rules use the familiar slash
|
|
40
40
|
* form such as `react/jsx-key` or `demo/no-demo`.
|
|
41
|
+
*
|
|
42
|
+
* Project-scoped contributor rules use this same map, but their configuration
|
|
43
|
+
* must come from entries without a `files` selector.
|
|
41
44
|
*/
|
|
42
45
|
rules?: ITtscLintRules;
|
|
43
46
|
|