@ttsc/lint 0.12.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/defaultFormat.d.ts +10 -11
- package/lib/defaultFormat.js +10 -11
- package/lib/defaultFormat.js.map +1 -1
- package/lib/index.js +233 -135
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +2 -2
- package/lib/structures/ITtscLintFormatConfig.d.ts +53 -55
- package/lib/structures/ITtscLintPluginConfig.d.ts +10 -77
- package/lib/structures/ITtscLintPluginMeta.d.ts +9 -1
- package/lib/structures/TtscLintRule.d.ts +10 -1
- package/lib/structures/TtscLintRuleMap.d.ts +2 -2
- package/lib/structures/TtscLintRuleOptions.d.ts +14 -0
- package/linthost/ast_helpers.go +80 -11
- package/linthost/compile.go +116 -112
- package/linthost/config.go +647 -687
- package/linthost/config_format.go +272 -247
- package/linthost/contrib_adapter.go +7 -0
- package/linthost/directives.go +116 -0
- package/linthost/dispatch.go +33 -33
- package/linthost/engine.go +156 -43
- package/linthost/fix.go +47 -27
- package/linthost/flags_gen.go +31 -0
- package/linthost/format.go +119 -3
- package/linthost/host.go +115 -5
- package/linthost/print_dispatch.go +128 -28
- package/linthost/print_doc.go +23 -0
- package/linthost/print_engine.go +168 -4
- package/linthost/print_nodes_array.go +17 -7
- package/linthost/print_nodes_call.go +138 -20
- package/linthost/print_nodes_function.go +353 -0
- package/linthost/print_nodes_imports.go +46 -29
- package/linthost/print_nodes_list.go +86 -5
- package/linthost/print_nodes_object.go +56 -11
- package/linthost/rules_arrays.go +5 -2
- package/linthost/rules_debugger.go +3 -2
- package/linthost/rules_dupes.go +7 -4
- package/linthost/rules_empty.go +3 -2
- package/linthost/rules_escape.go +35 -3
- package/linthost/rules_eval.go +3 -0
- package/linthost/rules_finally.go +11 -0
- package/linthost/rules_format_jsdoc.go +7 -0
- package/linthost/rules_format_print_width.go +270 -15
- package/linthost/rules_format_quotes.go +3 -0
- package/linthost/rules_format_sort_imports.go +4 -0
- package/linthost/rules_gap.go +75 -3
- package/linthost/rules_imports.go +5 -7
- package/linthost/rules_logic.go +75 -5
- package/linthost/rules_loops.go +4 -0
- package/linthost/rules_misc.go +4 -2
- package/linthost/rules_params.go +7 -11
- package/linthost/rules_problems.go +89 -22
- package/linthost/rules_promise.go +15 -0
- package/linthost/rules_protos.go +3 -2
- package/linthost/rules_self.go +6 -0
- package/linthost/rules_strings.go +10 -0
- package/linthost/rules_suggestions.go +174 -9
- package/linthost/rules_throw.go +2 -0
- package/linthost/rules_ts.go +13 -0
- package/linthost/rules_ts_extra.go +25 -8
- package/linthost/rules_var.go +15 -2
- package/package.json +3 -3
- package/plugin/main.go +4 -4
- package/rule/astutil/astutil.go +12 -0
- package/rule/rule.go +123 -123
- package/src/defaultFormat.ts +10 -11
- package/src/index.ts +257 -168
- package/src/structures/ITtscLintConfig.ts +2 -2
- package/src/structures/ITtscLintFormatConfig.ts +53 -55
- package/src/structures/ITtscLintPluginConfig.ts +10 -83
- package/src/structures/ITtscLintPluginMeta.ts +9 -1
- package/src/structures/TtscLintRule.ts +10 -1
- package/src/structures/TtscLintRuleMap.ts +17 -18
- package/src/structures/TtscLintRuleOptions.ts +15 -0
- package/linthost/eslint_runtime.go +0 -320
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Extended @typescript-eslint rules that work off the AST alone (no
|
|
2
|
+
// checker, no scope analysis). Rules here complement the core set in
|
|
3
|
+
// rules_ts.go; the split is by recommendation tier — this file covers
|
|
4
|
+
// rules that appear in typescript-eslint strict, stylistic, or as
|
|
5
|
+
// commonly-requested extras. Each rule is registered in init() below.
|
|
6
6
|
package linthost
|
|
7
7
|
|
|
8
8
|
import (
|
|
@@ -187,6 +187,10 @@ func (noNonNullAssertedOptionalChain) Check(ctx *Context, node *shimast.Node) {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// containsOptionalChain reports whether node or any of its left-hand
|
|
191
|
+
// sub-expressions uses the optional-chaining operator (?.). Only descends
|
|
192
|
+
// into PropertyAccessExpression, ElementAccessExpression, and
|
|
193
|
+
// CallExpression chains — stops at any other node kind.
|
|
190
194
|
func containsOptionalChain(node *shimast.Node) bool {
|
|
191
195
|
if node == nil {
|
|
192
196
|
return false
|
|
@@ -333,6 +337,8 @@ func (preferForOf) Check(ctx *Context, node *shimast.Node) {
|
|
|
333
337
|
ctx.Report(node, "Prefer a 'for-of' loop instead of a 'for' loop with this simple iteration.")
|
|
334
338
|
}
|
|
335
339
|
|
|
340
|
+
// isCounterIncrement reports whether node is a prefix or postfix `++`
|
|
341
|
+
// applied to the identifier named counter. Used by prefer-for-of.
|
|
336
342
|
func isCounterIncrement(node *shimast.Node, counter string) bool {
|
|
337
343
|
switch node.Kind {
|
|
338
344
|
case shimast.KindPostfixUnaryExpression:
|
|
@@ -501,6 +507,11 @@ func (consistentTypeImports) Check(ctx *Context, node *shimast.Node) {
|
|
|
501
507
|
ctx.Report(node, "All imports in the declaration are only used as types. Use `import type`.")
|
|
502
508
|
}
|
|
503
509
|
|
|
510
|
+
// allUsesAreTypeOnly reports whether every reference to any of the given
|
|
511
|
+
// names in the subtree rooted at root occurs inside a type-only position
|
|
512
|
+
// (TypeReferenceNode, TypeAliasDeclaration, InterfaceDeclaration, etc.).
|
|
513
|
+
// A reference inside another ImportDeclaration is skipped entirely.
|
|
514
|
+
// Returns false as soon as a value-position reference is found.
|
|
504
515
|
func allUsesAreTypeOnly(root *shimast.Node, names []string) bool {
|
|
505
516
|
want := map[string]bool{}
|
|
506
517
|
for _, n := range names {
|
|
@@ -675,6 +686,9 @@ func (adjacentOverloadSignatures) Check(ctx *Context, node *shimast.Node) {
|
|
|
675
686
|
}
|
|
676
687
|
}
|
|
677
688
|
|
|
689
|
+
// containerMembers returns the direct child member/statement list of a
|
|
690
|
+
// container node (interface, type literal, class, module block, or source
|
|
691
|
+
// file). Returns nil for node kinds that don't have member lists.
|
|
678
692
|
func containerMembers(node *shimast.Node) []*shimast.Node {
|
|
679
693
|
switch node.Kind {
|
|
680
694
|
case shimast.KindInterfaceDeclaration:
|
|
@@ -711,6 +725,12 @@ func containerMembers(node *shimast.Node) []*shimast.Node {
|
|
|
711
725
|
return nil
|
|
712
726
|
}
|
|
713
727
|
|
|
728
|
+
// overloadName extracts the canonical name and kind of an overloadable
|
|
729
|
+
// member node. Returns (name, kind, true) for method signatures, method
|
|
730
|
+
// declarations, function declarations, call signatures, and construct
|
|
731
|
+
// signatures; otherwise returns ("", 0, false). Call and construct
|
|
732
|
+
// signatures use a synthesized name that includes the kind string so
|
|
733
|
+
// they compare equal only to other signatures of the same shape.
|
|
714
734
|
func overloadName(m *shimast.Node) (string, shimast.Kind, bool) {
|
|
715
735
|
if m == nil {
|
|
716
736
|
return "", 0, false
|
|
@@ -737,9 +757,6 @@ func overloadName(m *shimast.Node) (string, shimast.Kind, bool) {
|
|
|
737
757
|
return "", 0, false
|
|
738
758
|
}
|
|
739
759
|
|
|
740
|
-
// no-this-alias-helper: shared helpers for ts rules above.
|
|
741
|
-
var _ = struct{}{}
|
|
742
|
-
|
|
743
760
|
func init() {
|
|
744
761
|
Register(noConfusingNonNullAssertion{})
|
|
745
762
|
Register(noDuplicateEnumValues{})
|
package/linthost/rules_var.go
CHANGED
|
@@ -31,7 +31,8 @@ func (noVar) Check(ctx *Context, node *shimast.Node) {
|
|
|
31
31
|
// prefer-const: flag `let` declarations whose binding is never reassigned.
|
|
32
32
|
// This follows ESLint's core rule for the common AST-local cases. It is
|
|
33
33
|
// intentionally conservative: destructuring and declaration-only `let`
|
|
34
|
-
// variables
|
|
34
|
+
// variables (those without an initializer and not in a for-of/for-in
|
|
35
|
+
// header) are skipped until the lint host grows full scope/data-flow state.
|
|
35
36
|
// ESLint canonical: https://eslint.org/docs/latest/rules/prefer-const
|
|
36
37
|
type preferConst struct{}
|
|
37
38
|
|
|
@@ -70,7 +71,7 @@ func (preferConst) Check(ctx *Context, node *shimast.Node) {
|
|
|
70
71
|
if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind) {
|
|
71
72
|
return
|
|
72
73
|
}
|
|
73
|
-
for _, name := range
|
|
74
|
+
for _, name := range assignmentTargetNames(expr.Left) {
|
|
74
75
|
assigned[name] = true
|
|
75
76
|
}
|
|
76
77
|
case shimast.KindPrefixUnaryExpression:
|
|
@@ -115,6 +116,10 @@ func (preferConst) Check(ctx *Context, node *shimast.Node) {
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
// isSingleDeclarationList reports whether the VariableDeclarationList node
|
|
120
|
+
// declares exactly one binding, which is required before the `let` keyword
|
|
121
|
+
// can safely be rewritten to `const` (a multi-binding list shares a single
|
|
122
|
+
// keyword, so replacing just one binding's keyword is not valid).
|
|
118
123
|
func isSingleDeclarationList(node *shimast.Node) bool {
|
|
119
124
|
if node == nil {
|
|
120
125
|
return false
|
|
@@ -123,6 +128,14 @@ func isSingleDeclarationList(node *shimast.Node) bool {
|
|
|
123
128
|
return list != nil && list.Declarations != nil && len(list.Declarations.Nodes) == 1
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
// isConstEligibleLetDeclaration reports whether a `let` VariableDeclaration
|
|
132
|
+
// node is eligible for prefer-const analysis. A declaration is eligible when:
|
|
133
|
+
// - it has an initializer (the value is set immediately), or
|
|
134
|
+
// - it is the loop variable of a for-in or for-of statement (e.g. `for (let k of m)`).
|
|
135
|
+
//
|
|
136
|
+
// For-statement initializers (`for (let i = 0; …)`) are eligible only when
|
|
137
|
+
// the declaration list is a single binding; the loop index is a well-known
|
|
138
|
+
// reassignment target so multi-binding for-statement lists are excluded.
|
|
126
139
|
func isConstEligibleLetDeclaration(node *shimast.Node, decl *shimast.VariableDeclaration) bool {
|
|
127
140
|
if decl.Initializer != nil {
|
|
128
141
|
if node.Parent != nil && node.Parent.Parent != nil && node.Parent.Parent.Kind == shimast.KindForStatement {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Reference ttsc plugin: ESLint-style lint rules hosted in the same Program/Checker as the type-check pass.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"src"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
36
|
+
"@typescript/native-preview": "7.0.0-dev.20260522.1",
|
|
37
37
|
"@types/node": "^25.3.0",
|
|
38
38
|
"rimraf": "^6.1.2",
|
|
39
|
-
"ttsc": "0.
|
|
39
|
+
"ttsc": "0.13.0"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
package/plugin/main.go
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Command @ttsc/lint is the native backend for the `@ttsc/lint` plugin.
|
|
2
2
|
//
|
|
3
|
-
// The plugin host (ttsc / ttsx) spawns this binary with one of
|
|
3
|
+
// The plugin host (ttsc / ttsx) spawns this binary with one of six
|
|
4
4
|
// subcommands:
|
|
5
5
|
//
|
|
6
6
|
// - `version` / `-v` / `--version` — print the binary banner.
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
package main
|
|
26
26
|
|
|
27
27
|
import (
|
|
28
|
-
|
|
28
|
+
"os"
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
"github.com/samchon/ttsc/packages/lint/linthost"
|
|
31
31
|
)
|
|
32
32
|
|
|
33
33
|
func main() {
|
|
34
|
-
|
|
34
|
+
os.Exit(linthost.Main(os.Args[1:]))
|
|
35
35
|
}
|
package/rule/astutil/astutil.go
CHANGED
|
@@ -68,6 +68,9 @@ func KeywordStart(file *shimast.SourceFile, node *shimast.Node, keyword string)
|
|
|
68
68
|
if limit > len(src) {
|
|
69
69
|
limit = len(src)
|
|
70
70
|
}
|
|
71
|
+
// Scan at most 32 bytes past the trivia-adjusted start. Declaration keywords
|
|
72
|
+
// ("var", "let", "const", etc.) always appear within the first few bytes of
|
|
73
|
+
// a node's token range; the cap avoids runaway scanning on malformed nodes.
|
|
71
74
|
for i := pos; i+len(keyword) <= limit && i < pos+32; i++ {
|
|
72
75
|
end = i + len(keyword)
|
|
73
76
|
if strings.HasPrefix(src[i:], keyword) &&
|
|
@@ -139,6 +142,15 @@ func TokenRange(file *shimast.SourceFile, node *shimast.Node) (int, int) {
|
|
|
139
142
|
return pos, end
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
// isIdentifierPart reports whether the ASCII byte ch can appear inside a
|
|
146
|
+
// JavaScript/TypeScript identifier (letters, digits, underscore, dollar sign).
|
|
147
|
+
// Used to detect word boundaries when searching for keyword tokens so that
|
|
148
|
+
// a search for "let" does not match "letters".
|
|
149
|
+
// Note: this is a byte-level check; non-ASCII identifier characters (e.g.
|
|
150
|
+
// Unicode letters) are treated as non-identifier bytes, which is conservative
|
|
151
|
+
// — the keyword boundary check may produce a false positive only for source
|
|
152
|
+
// files that use non-ASCII characters immediately adjacent to a keyword,
|
|
153
|
+
// an extremely rare pattern in practice.
|
|
142
154
|
func isIdentifierPart(ch byte) bool {
|
|
143
155
|
return (ch >= 'a' && ch <= 'z') ||
|
|
144
156
|
(ch >= 'A' && ch <= 'Z') ||
|
package/rule/rule.go
CHANGED
|
@@ -21,30 +21,30 @@
|
|
|
21
21
|
//
|
|
22
22
|
// Example contributor:
|
|
23
23
|
//
|
|
24
|
-
//
|
|
24
|
+
// package myrules
|
|
25
25
|
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
26
|
+
// import (
|
|
27
|
+
// shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
28
|
+
// "github.com/samchon/ttsc/packages/lint/rule"
|
|
29
|
+
// )
|
|
30
30
|
//
|
|
31
|
-
//
|
|
31
|
+
// func init() { rule.Register(noTodoComment{}) }
|
|
32
32
|
//
|
|
33
|
-
//
|
|
33
|
+
// type noTodoComment struct{}
|
|
34
34
|
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
35
|
+
// func (noTodoComment) Name() string { return "demo/no-todo-comment" }
|
|
36
|
+
// func (noTodoComment) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
37
|
+
// func (noTodoComment) Check(ctx *rule.Context, node *shimast.Node) {
|
|
38
|
+
// // ctx.File, ctx.Checker, ctx.Severity available; ctx.Report(node, msg)
|
|
39
|
+
// // or ctx.ReportRange(pos, end, msg) push a finding through the engine.
|
|
40
|
+
// }
|
|
41
41
|
package rule
|
|
42
42
|
|
|
43
43
|
import (
|
|
44
|
-
|
|
44
|
+
"encoding/json"
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
47
|
+
shimchecker "github.com/microsoft/typescript-go/shim/checker"
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
// Severity mirrors the engine's three-level severity ladder. The
|
|
@@ -53,31 +53,31 @@ import (
|
|
|
53
53
|
type Severity int
|
|
54
54
|
|
|
55
55
|
const (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
// SeverityOff means the rule is disabled. Engine skips dispatch.
|
|
57
|
+
SeverityOff Severity = iota
|
|
58
|
+
// SeverityWarn produces a warning diagnostic (does not change exit
|
|
59
|
+
// code).
|
|
60
|
+
SeverityWarn
|
|
61
|
+
// SeverityError produces an error diagnostic and fails the command.
|
|
62
|
+
SeverityError
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
// Rule is the contract every contributor rule satisfies. Mirrors the
|
|
66
66
|
// internal host interface so the host can dispatch via a thin adapter
|
|
67
67
|
// without re-implementing the engine.
|
|
68
68
|
type Rule interface {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// Name is the identifier users put in their `rules` map.
|
|
70
|
+
// Conventionally namespaced as "<plugin-namespace>/<rule-name>" to
|
|
71
|
+
// avoid colliding with built-in rule names.
|
|
72
|
+
Name() string
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
// Visits returns the AST kinds the rule cares about. The engine only
|
|
75
|
+
// dispatches to rules that registered for the visited node's kind.
|
|
76
|
+
Visits() []shimast.Kind
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// Check is invoked once per relevant node. Use `ctx.Report` /
|
|
79
|
+
// `ctx.ReportRange` to emit findings.
|
|
80
|
+
Check(ctx *Context, node *shimast.Node)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// FormatRule is an optional marker contributors implement when a rule
|
|
@@ -93,20 +93,20 @@ type Rule interface {
|
|
|
93
93
|
// returning `false` is equivalent to not implementing the interface at
|
|
94
94
|
// all, and the host treats either form the same way.
|
|
95
95
|
type FormatRule interface {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
Rule
|
|
97
|
+
IsFormat() bool
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Reporter is the engine-supplied callback that records a finding. The
|
|
101
101
|
// host implements this and passes it to `NewContext` when invoking a
|
|
102
102
|
// contributor rule.
|
|
103
103
|
type Reporter interface {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
// Report records a finding at the given node's source range.
|
|
105
|
+
Report(node *shimast.Node, message string)
|
|
106
|
+
// ReportRange records a finding at an explicit byte range inside the
|
|
107
|
+
// current file. Use this when the rule wants to highlight a
|
|
108
|
+
// sub-token.
|
|
109
|
+
ReportRange(pos, end int, message string)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// FixReporter is the optional extension a host implements to receive
|
|
@@ -122,8 +122,8 @@ type Reporter interface {
|
|
|
122
122
|
// a fake that wants the fix path must implement BOTH `ReportFix` and
|
|
123
123
|
// `ReportRangeFix`.
|
|
124
124
|
type FixReporter interface {
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
ReportFix(node *shimast.Node, message string, edits ...TextEdit)
|
|
126
|
+
ReportRangeFix(pos, end int, message string, edits ...TextEdit)
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// TextEdit is one byte-range replacement offered by an autofixable finding.
|
|
@@ -143,9 +143,9 @@ type FixReporter interface {
|
|
|
143
143
|
// falls inside a deletion range. Design fixes so each finding emits one
|
|
144
144
|
// contiguous TextEdit covering the entire replacement region.
|
|
145
145
|
type TextEdit struct {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
Pos int
|
|
147
|
+
End int
|
|
148
|
+
Text string
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Context is the per-(file, rule) handle the engine passes to `Check`.
|
|
@@ -153,70 +153,70 @@ type TextEdit struct {
|
|
|
153
153
|
// contributors call `ctx.Report` / `ctx.ReportRange` directly through
|
|
154
154
|
// this Context rather than touching the reporter.
|
|
155
155
|
type Context struct {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
// File is the source file currently being walked. Always non-nil
|
|
157
|
+
// when `Check` is invoked.
|
|
158
|
+
File *shimast.SourceFile
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
// Checker is the host's tsgo type checker. Available for type-aware
|
|
161
|
+
// rules; nil-safe enough that AST-only rules can ignore it.
|
|
162
|
+
Checker *shimchecker.Checker
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
// Severity is the rule's resolved severity for this file. Already
|
|
165
|
+
// filtered by the engine — rules do not need to check for
|
|
166
|
+
// SeverityOff.
|
|
167
|
+
Severity Severity
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
// Options is the raw JSON blob the user wrote in the second slot of
|
|
170
|
+
// their `[severity, options]` rule configuration tuple. Nil when the
|
|
171
|
+
// rule was configured with a bare severity literal. Contributors that
|
|
172
|
+
// accept options decode the blob into their own struct via
|
|
173
|
+
// `(*Context).DecodeOptions`.
|
|
174
|
+
Options json.RawMessage
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
reporter Reporter
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// NewContext constructs a Context for the engine to pass into a
|
|
180
180
|
// contributor rule's `Check`. Reserved for host code; contributors
|
|
181
181
|
// should not need to call this.
|
|
182
182
|
func NewContext(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
file *shimast.SourceFile,
|
|
184
|
+
checker *shimchecker.Checker,
|
|
185
|
+
severity Severity,
|
|
186
|
+
options json.RawMessage,
|
|
187
|
+
reporter Reporter,
|
|
188
188
|
) *Context {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
189
|
+
return &Context{
|
|
190
|
+
File: file,
|
|
191
|
+
Checker: checker,
|
|
192
|
+
Severity: severity,
|
|
193
|
+
Options: options,
|
|
194
|
+
reporter: reporter,
|
|
195
|
+
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// DecodeOptions unmarshals the rule's options blob into `out`. Returns
|
|
199
199
|
// nil with no side effect when the rule was configured with severity
|
|
200
200
|
// alone, so contributors can write:
|
|
201
201
|
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
202
|
+
// var opts myRuleOptions
|
|
203
|
+
// _ = ctx.DecodeOptions(&opts)
|
|
204
|
+
// // opts now holds either the user's settings or the zero value.
|
|
205
205
|
func (c *Context) DecodeOptions(out interface{}) error {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
if c == nil || len(c.Options) == 0 {
|
|
207
|
+
return nil
|
|
208
|
+
}
|
|
209
|
+
return json.Unmarshal(c.Options, out)
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// Report records a finding at the given node's source range. Silently
|
|
213
213
|
// ignored when severity is `off` (defensive — the engine already filters
|
|
214
214
|
// by severity before invoking Check) or when no reporter is attached.
|
|
215
215
|
func (c *Context) Report(node *shimast.Node, message string) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff || node == nil {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
c.reporter.Report(node, message)
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
// ReportFix records a finding at the given node's source range with optional
|
|
@@ -224,28 +224,28 @@ func (c *Context) Report(node *shimast.Node, message string) {
|
|
|
224
224
|
// diagnostic without edits.
|
|
225
225
|
// Treat edits as best-effort: design the rule so the diagnostic alone is useful.
|
|
226
226
|
func (c *Context) ReportFix(node *shimast.Node, message string, edits ...TextEdit) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
227
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff || node == nil {
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
if len(edits) == 0 {
|
|
231
|
+
c.reporter.Report(node, message)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
fixer, ok := c.reporter.(FixReporter)
|
|
235
|
+
if !ok {
|
|
236
|
+
c.reporter.Report(node, message)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
fixer.ReportFix(node, message, edits...)
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
// ReportRange records a finding at an explicit byte range inside the
|
|
243
243
|
// current file.
|
|
244
244
|
func (c *Context) ReportRange(pos, end int, message string) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff {
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
c.reporter.ReportRange(pos, end, message)
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// ReportRangeFix records a finding at an explicit byte range with optional
|
|
@@ -253,19 +253,19 @@ func (c *Context) ReportRange(pos, end int, message string) {
|
|
|
253
253
|
// diagnostic without edits.
|
|
254
254
|
// Treat edits as best-effort: design the rule so the diagnostic alone is useful.
|
|
255
255
|
func (c *Context) ReportRangeFix(pos, end int, message string, edits ...TextEdit) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
if c == nil || c.reporter == nil || c.Severity == SeverityOff {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
if len(edits) == 0 {
|
|
260
|
+
c.reporter.ReportRange(pos, end, message)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
fixer, ok := c.reporter.(FixReporter)
|
|
264
|
+
if !ok {
|
|
265
|
+
c.reporter.ReportRange(pos, end, message)
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
fixer.ReportRangeFix(pos, end, message, edits...)
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
var registry []Rule
|
|
@@ -275,17 +275,17 @@ var registry []Rule
|
|
|
275
275
|
// — the host's adapter layer surfaces collisions with a clearer error
|
|
276
276
|
// than a raw panic.
|
|
277
277
|
func Register(r Rule) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
if r == nil {
|
|
279
|
+
panic("rule: Register called with nil rule")
|
|
280
|
+
}
|
|
281
|
+
registry = append(registry, r)
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// Registered returns every contributor rule registered via `Register`.
|
|
285
285
|
// Called once by the host during engine bootstrap. The returned slice is
|
|
286
286
|
// a defensive copy so the host cannot mutate the registry.
|
|
287
287
|
func Registered() []Rule {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
out := make([]Rule, len(registry))
|
|
289
|
+
copy(out, registry)
|
|
290
|
+
return out
|
|
291
291
|
}
|
package/src/defaultFormat.ts
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import type { ITtscLintFormatConfig } from "./structures/ITtscLintFormatConfig";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Documented defaults for the `format` block's
|
|
4
|
+
* Documented defaults for the `format` block's _always-on_ rules
|
|
5
5
|
* (`format/semi`, `format/quotes`, `format/trailing-comma`,
|
|
6
6
|
* `format/print-width`).
|
|
7
7
|
*
|
|
8
8
|
* Exported so users can spread defaults next to overrides:
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Import { defaultFormat, type ITtscLintConfig } from "@ttsc/lint";
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* } satisfies ITtscLintConfig;
|
|
12
|
+
* Export default { format: { ...defaultFormat, printWidth: 100 }, } satisfies
|
|
13
|
+
* ITtscLintConfig;
|
|
15
14
|
*
|
|
16
|
-
* The values mirror Prettier 1:1 except for the documented
|
|
17
|
-
*
|
|
15
|
+
* The values mirror Prettier 1:1 except for the documented `endOfLine`
|
|
16
|
+
* narrowing (no `"cr"` / `"auto"`).
|
|
18
17
|
*
|
|
19
|
-
* Notably absent: `importOrder` and `jsdoc`. `format/sort-imports`
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* Notably absent: `importOrder` and `jsdoc`. `format/sort-imports` and
|
|
19
|
+
* `format/jsdoc` are opt-in by setting their corresponding fields; the defaults
|
|
20
|
+
* const only seeds the rules that turn on unconditionally with a non-empty
|
|
21
|
+
* `format` block.
|
|
23
22
|
*/
|
|
24
23
|
export const defaultFormat = Object.freeze({
|
|
25
24
|
severity: "off",
|