@ttsc/lint 0.26.2 → 0.28.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/linthost/serve.go CHANGED
@@ -235,9 +235,11 @@ func RunLSPServe(in io.Reader, out io.Writer, args []string) int {
235
235
  return 2
236
236
  }
237
237
  residentPrograms = newResidentProgramCache()
238
+ residentRules = &residentRuleCache{}
238
239
  defer func() {
239
240
  residentPrograms.invalidate()
240
241
  residentPrograms = nil
242
+ residentRules = nil
241
243
  }()
242
244
  encoder := json.NewEncoder(out)
243
245
  // ReadString imposes no line-length limit: a request is small, but keeping the
@@ -276,6 +278,12 @@ func handleServeLSPLine(line string, base *lspCommandOptions, encoder *json.Enco
276
278
  return
277
279
  }
278
280
  if req.Invalidate {
281
+ // The Program only. The rule memo validates itself against the files it was
282
+ // loaded from, so it needs no help from a client — and dropping it here
283
+ // would re-evaluate the project's configuration on exactly the requests
284
+ // that carry this control, which is every request from a consumer that
285
+ // cannot localize a source change. That is the cost the memo exists to
286
+ // remove.
279
287
  residentPrograms.invalidate()
280
288
  }
281
289
  if len(req.Changed) > 0 {
@@ -310,6 +318,17 @@ func handleServeLSPLine(line string, base *lspCommandOptions, encoder *json.Enco
310
318
  case "lsp-hints":
311
319
  result, code := computeLSPHints(&opts)
312
320
  encodeServeResult(encoder, result, code)
321
+ case "graph-nodes":
322
+ // Neither of these describes a document, so neither carries a uri. They
323
+ // describe the project, and they join the daemon for the reason the other
324
+ // read verbs did: a consumer that asks them again whenever a file it
325
+ // watches moves would otherwise pay a process, a plugin load, and a Program
326
+ // for every edit.
327
+ result, code := computeGraphNodes(&opts)
328
+ encodeServeResult(encoder, result, code)
329
+ case "project-inputs":
330
+ result, code := computeProjectInputs(&opts)
331
+ encodeServeResult(encoder, result, code)
313
332
  case "lsp-command-ids":
314
333
  encodeServeResult(encoder, lspCommandIDs(), 0)
315
334
  case "lsp-code-action-kinds":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/lint",
3
- "version": "0.26.2",
3
+ "version": "0.28.0",
4
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",
@@ -37,7 +37,7 @@
37
37
  "@types/node": "^25.3.0",
38
38
  "rimraf": "^6.1.2",
39
39
  "typescript": "^7.0.2",
40
- "ttsc": "0.26.2"
40
+ "ttsc": "0.28.0"
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",
package/rule/graph.go ADDED
@@ -0,0 +1,150 @@
1
+ package rule
2
+
3
+ import "encoding/json"
4
+
5
+ // GraphNodeKind names what a published graph node is.
6
+ //
7
+ // The vocabulary is fixed by the host rather than chosen by the contributor,
8
+ // because a consumer has to know what it received: a kind it does not recognize
9
+ // is a node it cannot rank, colour, or contain. A contributor publishing an
10
+ // unrecognized kind has its node dropped, and the drop is a declared outcome
11
+ // rather than a silent one.
12
+ //
13
+ // It is deliberately not evidence-specific. These are the shapes an artifact an
14
+ // author can cite actually has — a document and its sections, a data model and
15
+ // its fields, an API operation — and any contributor that can materialize one
16
+ // publishes it here.
17
+ type GraphNodeKind string
18
+
19
+ const (
20
+ // GraphNodeMarkdownDocument is a whole Markdown document.
21
+ GraphNodeMarkdownDocument GraphNodeKind = "markdown_document"
22
+ // GraphNodeMarkdownSection is one heading and the span it opens. It carries
23
+ // the heading's text, never the section's content.
24
+ GraphNodeMarkdownSection GraphNodeKind = "markdown_section"
25
+ // GraphNodePrismaModel is a Prisma model declaration.
26
+ GraphNodePrismaModel GraphNodeKind = "prisma_model"
27
+ // GraphNodePrismaColumn is a scalar field of a Prisma model.
28
+ GraphNodePrismaColumn GraphNodeKind = "prisma_column"
29
+ // GraphNodePrismaRelation is a relation field of a Prisma model. It is its
30
+ // own kind because a relation has two sides and only one usually carries the
31
+ // declaration, which a column never does.
32
+ GraphNodePrismaRelation GraphNodeKind = "prisma_relation"
33
+ // GraphNodeSwaggerOperation is one method-and-path operation of an API
34
+ // document.
35
+ GraphNodeSwaggerOperation GraphNodeKind = "swagger_operation"
36
+ )
37
+
38
+ // GraphNodeKinds returns every kind a consumer accepts, in declaration order.
39
+ //
40
+ // A consumer seeds its vocabulary from this rather than from a list of its own:
41
+ // a kind added to the block above and not to a consumer's map is a node drawn
42
+ // or ranked as something it is not.
43
+ func GraphNodeKinds() []GraphNodeKind {
44
+ return []GraphNodeKind{
45
+ GraphNodeMarkdownDocument,
46
+ GraphNodeMarkdownSection,
47
+ GraphNodePrismaModel,
48
+ GraphNodePrismaColumn,
49
+ GraphNodePrismaRelation,
50
+ GraphNodeSwaggerOperation,
51
+ }
52
+ }
53
+
54
+ // GraphNode is one artifact a declaration's documentation can cite.
55
+ //
56
+ // It is a value, not a behavior, for the same reason Hint is: the host
57
+ // serializes the set and hands it to a consumer that reads it long after the
58
+ // lint process exited. What travels is what the consumer can answer from.
59
+ //
60
+ // The node is an index entry, never content. A section carries its heading and
61
+ // where it starts; the text under that heading is read from the file when
62
+ // someone actually needs it, exactly as a function body is.
63
+ type GraphNode struct {
64
+ // Address is the identity a citation names, verbatim — `docs/sale.md#pricing`,
65
+ // `prisma:Sale.price`, `POST:/orders/{orderId}`.
66
+ //
67
+ // The rule that produced it owns the grammar. A consumer keys on the string
68
+ // and parses none of it: the address forms come from a Markdown anchor
69
+ // generator, a Prisma parser, and an OpenAPI normalizer, and re-deriving any
70
+ // of them outside the rule that owns it would be a second implementation of a
71
+ // published contract.
72
+ Address string `json:"address"`
73
+
74
+ // Kind is what this node is. A node whose kind is not in GraphNodeKinds is
75
+ // dropped by the consumer.
76
+ Kind GraphNodeKind `json:"kind"`
77
+
78
+ // Readable is the human-facing name — a heading's own text, a model's name.
79
+ // Empty when the artifact has none beyond its address.
80
+ Readable string `json:"readable,omitempty"`
81
+
82
+ // Parent is the Address of the node containing this one: a section's document
83
+ // or enclosing section, a column's model. Empty at the top of a containment
84
+ // chain. A parent naming no published node is dropped rather than fabricated.
85
+ Parent string `json:"parent,omitempty"`
86
+
87
+ // File is where the artifact lives, as the rule spells it. Empty when the
88
+ // artifact has no file — an API operation is named by method and path, and
89
+ // which document declared it is not part of its identity.
90
+ File string `json:"file,omitempty"`
91
+
92
+ // Line is the 1-based line the node starts on, or 0 when it has no position.
93
+ Line int `json:"line,omitempty"`
94
+
95
+ // Aliases are the additional addresses this same node answers to, when the
96
+ // rule exposes it by more than one path. They resolve to this node rather
97
+ // than to copies of it, so an artifact reachable twice is one node.
98
+ Aliases []string `json:"aliases,omitempty"`
99
+ }
100
+
101
+ // GraphContext is the read-only handle the host passes to GraphNodes.
102
+ //
103
+ // It mirrors HintContext exactly, and for the same reason: a rule value is
104
+ // stateless, so without State here a projection could only ever return
105
+ // constants.
106
+ type GraphContext struct {
107
+ // Identity names the Program these nodes were built for, as during Check.
108
+ Identity ProjectIdentity
109
+
110
+ // State is the value the rule passed to ProjectContext.SetState.
111
+ State any
112
+
113
+ // Severity and Options are the resolved configuration Check ran under.
114
+ Severity Severity
115
+ Options json.RawMessage
116
+ }
117
+
118
+ // DecodeOptions unmarshals the configured options into out. A missing options
119
+ // tuple leaves out unchanged and returns nil.
120
+ func (c *GraphContext) DecodeOptions(out interface{}) error {
121
+ if c == nil || len(c.Options) == 0 {
122
+ return nil
123
+ }
124
+ return json.Unmarshal(c.Options, out)
125
+ }
126
+
127
+ // GraphRule is an optional marker a ProjectRule implements to publish the
128
+ // artifacts a declaration's documentation can cite.
129
+ //
130
+ // The gate is HintRule's, for the same reasons: called at most once per Program,
131
+ // always after Check, only when a consumer asks — never during `ttsc check` —
132
+ // and never unless Check passed and published state. A rule configured off is
133
+ // never asked, so `off` means no nodes with no code in the rule.
134
+ //
135
+ // Pull, not push. A set of artifacts is a projection of FINISHED state; a rule
136
+ // pushing nodes while building that state would publish the ones it had found
137
+ // so far rather than the ones the project has.
138
+ //
139
+ // What crosses this boundary is what an artifact *is*, never what the rule
140
+ // decided about it. No coverage, no cardinality, no policy, no diagnostic:
141
+ // those are the linter's product and it already delivers them as compile
142
+ // errors. A consumer that received them would hold a second, unmaintained
143
+ // answer to a question the linter already answers.
144
+ type GraphRule interface {
145
+ ProjectRule
146
+
147
+ // GraphNodes returns the artifacts this rule materialized, in any order.
148
+ // Containment is expressed by Parent rather than by position.
149
+ GraphNodes(ctx *GraphContext) []GraphNode
150
+ }