@loom-forge/language-service 0.1.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/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # @loom-forge/language-service
2
+
3
+ O **serviço de linguagem** do construtor: o que um inspetor, um canvas ou um editor de arquivo
4
+ perguntam sobre um documento de componentes — e a resposta na coordenada que o autor enxerga.
5
+ Dev/CI, **nunca no hot path de render**: ele arrasta o `@loom-forge/check`, e o `check` arrasta o
6
+ `@tslite/checker`.
7
+
8
+ ```bash
9
+ pnpm add -D @loom-forge/language-service
10
+ ```
11
+
12
+ ## A tese
13
+
14
+ Um serviço de linguagem **não é dono de nenhuma regra da linguagem**. Ele é dono de saber a quem
15
+ perguntar:
16
+
17
+ ```
18
+ COMPILAÇÃO @loom-forge/tslite, via o forge sintaxe, ilha vazia, o que o deploy recusa
19
+ TIPOS @loom-forge/check tipo, escopo, call-site, declaração
20
+ ```
21
+
22
+ O que ele acrescenta é o que nenhum dos dois responde: a resposta na **coordenada do documento**
23
+ — o `DocumentPath` do `@tslite/language-service`, o caminho real dentro do `ComponentDef` —, e o
24
+ **ambiente de um campo**: que nomes o campo enxerga de onde está, com que tipo, projetado da mesma
25
+ análise que o tipa.
26
+
27
+ É o mesmo desenho do `@node-actions/language-service`, sobre outro documento. O que é comum às
28
+ duas linguagens já vive no contrato do TSLite (`DocumentPath`, `FieldDiagnostic`, `byField`,
29
+ `withFieldDiagnostics`); o que é domínio — um `each` sem `as`, o slot de um componente, o placement
30
+ que o layout do pai publica — continua em dois, e é por isso que este package existe separado.
31
+
32
+ ## Uso
33
+
34
+ ```ts
35
+ import { createComponentService } from "@loom-forge/language-service";
36
+
37
+ const service = createComponentService({
38
+ profile, // o CapabilityProfile do deploy — default `DEFAULT_PROFILE` do bind
39
+ types, // o catálogo de tipos nomeados
40
+ contexts, // o catálogo de contextos (`nome → TypeRef`)
41
+ emitsOf, // o que cada peça do kit emite num evento (`descriptor.meta.emits`)
42
+ actions, // a porta de ação do host — a assinatura do que não é update local
43
+ commands, // o vocabulário de comando do host
44
+ });
45
+
46
+ service.diagnostics(defs); // FieldDiagnostic[] — compilação + tipos, por campo
47
+ service.diagnosticsOf(defs, "Card"); // idem, de um componente
48
+ service.envAt(defs, path); // o Env de um campo — o que o campo de expressão liga
49
+ service.compiled(defs, "Card"); // o artefato que o palco executa, com o que a compilação disse
50
+ service.system(defs); // o SystemIR — grafo, fatos, para quem julga regra
51
+ service.mounting(defs); // o que cada componente instala nos próprios slots
52
+ ```
53
+
54
+ **O mundo entra na construção; o documento é argumento.** As opções são o que existe fora do
55
+ documento — o deploy, os catálogos, o kit, o host — e mudam quando alguém publica algo. O
56
+ documento (`readonly ComponentDef[]`, o sistema inteiro) muda a cada edição, e por isso todo
57
+ método o recebe: a análise é memoizada pela **identidade** dele, e não há serviço "uma versão
58
+ atrás" para sincronizar.
59
+
60
+ ## Os diagnósticos — na coordenada do documento
61
+
62
+ ```ts
63
+ interface FieldDiagnostic {
64
+ path: DocumentPath; // { root: "Card", segments: ["body", "children", 1, "props", "title"] }
65
+ span?: Span; // relativo ao TEXTO DO CAMPO; ausente = o campo inteiro
66
+ code: string; // o contrato — `parse-error`, `no-such-member`, `not-assignable`…
67
+ message: string;
68
+ severity: "error" | "warning";
69
+ source: "compile" | "check"; // quem falou (`DIAGNOSTIC_SOURCE`)
70
+ }
71
+ ```
72
+
73
+ **O caminho é a lista, e é o caminho REAL.** `2` é índice, `"2"` é chave — a distinção fica no
74
+ tipo, e nenhuma string a preserva. Os segmentos andam o `ComponentDef` sem tradução: `valueAt(def,
75
+ segments)` é o valor do campo, `fieldText(def, segments)` é o texto dele, e `source.slice(span)`
76
+ sobre esse texto é o trecho aceso. `formatPath(path)` é a exibição
77
+ (`Card.body.children[1].props.title`); `pathKey(path)` é a chave de mapa. Nada é lido de volta.
78
+
79
+ | onde o campo mora | `segments` |
80
+ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
81
+ | um slot de um nó | `["body", "children", 1, "props", "title"]` |
82
+ | o `fallback` de um nó | `["body", "children", 1, "fallback", "when"]` |
83
+ | o `set` de um update | `["updates", "somar", "set", "n"]` |
84
+ | os `params` de uma intenção, e da cadeia dela | `["body", "children", 1, "events", "onClick", "params", "email"]` · `[…, "onClick", "then", "params", "valido"]` · `[…, "catch", "params", "motivo"]` |
85
+ | o `init` de um estado | `["state", "aberto", "init"]` |
86
+ | uma declaração de tipo | `["props", "n"]` |
87
+
88
+ **A compilação fala uma vez.** O serviço compila cada definição com o profile do deploy e entrega
89
+ o artefato ao `check`; o que a compilação recusou (sintaxe, ilha vazia, um nó fora do profile) sai
90
+ com `source: "compile"`, e o check não o repete. Um campo que não compilou é tipado como ausência
91
+ de valor — não como o texto que ficou nele.
92
+
93
+ Para um painel com **um campo por slot**, `byField` reparte tudo uma vez por análise, e
94
+ `withFieldDiagnostics` entrega ao campo os dele, presos ao texto commitado:
95
+
96
+ ```ts
97
+ import {
98
+ byField,
99
+ pathKey,
100
+ withFieldDiagnostics,
101
+ } from "@tslite/language-service";
102
+
103
+ const perField = byField(service.diagnostics(defs));
104
+ const mine = perField.get(pathKey(path)) ?? [];
105
+ ```
106
+
107
+ ## O ambiente de um campo — `envAt`
108
+
109
+ ```ts
110
+ const env = service.envAt(defs, {
111
+ root: "Lista",
112
+ segments: ["body", "children", 0, "children", 1, "props", "title"],
113
+ });
114
+ // dentro de um `each … as item`: `item` existe, com o tipo INFERIDO da coleção
115
+ ```
116
+
117
+ É a porta que a receita "o campo de um inspetor" do `@tslite/react` deixa do lado do domínio.
118
+ Ela responde pelo que o campo **enxerga de onde está**:
119
+
120
+ | o campo | o que enxerga |
121
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
122
+ | um slot na raiz do corpo | `props`, `state`, `ctx` (o que o componente usa) |
123
+ | um slot dentro de um `each` | o item, inferido da coleção — e os de fora |
124
+ | o próprio `each` | o de fora: a coleção é avaliada antes do laço |
125
+ | o `set` de um update | `params`, tipado com o que o update declara |
126
+ | o `init` de um estado | só `props` e `ctx` — é o que semeia o estado |
127
+ | os `params` de um efeito | o escopo do componente |
128
+ | os `params` de uma intenção (`events.onX`, `then`, `catch`) | o escopo do nó, mais a raiz `event` — o que a intenção RECEBEU, com o tipo que quem o produziu declarou: o `emits` do widget, o `resultOf` da ação anterior num `then`, o erro do contrato num `catch`, `undefined` depois de um update local (ADR-018 do root). Hoje o `envAt` devolve o escopo do nó; o frame por intenção nos `frames` do `check` é o que falta para o `event` entrar (§5 da ARCHITECTURE) |
129
+ | qualquer campo de um componente VIVO | `pending[ação]` — `true` do despacho ao resultado de uma ação do host; `Record<string, boolean>`. Num componente puro não existe |
130
+
131
+ **Ela responde por um campo vazio e por um que não compilou.** O ambiente de um campo é função do
132
+ que vem antes dele no documento, não do texto dele — quebrar o texto do campo não muda quem o
133
+ campo enxerga. Um campo novo, ainda sem expressão, já completa `item.` como o vizinho.
134
+
135
+ **E é uma projeção, não uma segunda análise.** O `check` baixa o componente num corpo TSL e o
136
+ `@tslite/checker` o tipa; cada nó e cada update têm o bloco em que os slots dele foram emitidos
137
+ (`Lowered.frames`), e o `scopeAt` do checker sobre esse bloco é o escopo do campo. Um serviço que
138
+ derivasse escopo por conta própria seria a segunda verdade que o ADR-208 do `check` existe para
139
+ impedir.
140
+
141
+ Com o `env` na mão, o campo é o do TSLite:
142
+
143
+ ```tsx
144
+ import { createHostService } from "@tslite/editor";
145
+ import { mustache, withFieldDiagnostics } from "@tslite/language-service";
146
+
147
+ const base = createHostService({ env, segmenter: mustache, mode: "expression", profile });
148
+ const field = withFieldDiagnostics(base, { text: committed, diagnostics: mine });
149
+ <ExpressionInput {...expressionFieldProps({ layout: "grow" })} service={field} value={committed} onCommit={…} />;
150
+ ```
151
+
152
+ Este package **não** importa o `@tslite/editor`: ele responde em domínio (`Env`, diagnóstico,
153
+ caminho), e quem monta a superfície — CodeMirror, Monaco, canvas — compõe.
154
+
155
+ ## O que ele recusa na montagem
156
+
157
+ Um catálogo de contextos que não resolve (`contexts: { "@app/x": "numero" }`) é erro de quem monta
158
+ o sistema, não diagnóstico do programa: `createComponentService` lança `LanguageServiceError`
159
+ (`code: "invalid-context-catalog"`, com os `issues`) — a mesma régua do `createBoundary` do
160
+ `@tslite/validate` para a colisão de nome.
161
+
162
+ ## Exports
163
+
164
+ `createComponentService` · `DIAGNOSTIC_SOURCE` · `LanguageServiceError` · `documentPathOf` ·
165
+ `segmentsOf` · `pathOf` · `nodePrefixOf` · os tipos `ComponentService`, `ComponentServiceOptions`,
166
+ `Document`.
@@ -0,0 +1,68 @@
1
+ import { Env } from '@tslite/checker';
2
+ import { Schema } from '@tslite/type-core';
3
+ import { CapabilityProfile } from '@tslite/capability';
4
+ import { FieldDiagnostic, DocumentPath, DocumentSegment } from '@tslite/language-service';
5
+ import { CommandScope } from '@loom-forge/core';
6
+ import { ComponentDef, SystemIR, Mounting, TypeRef, CompiledPath } from '@loom-forge/forge';
7
+ import { CompiledComponent, TypeCatalog, CheckComponentOptions, CheckIssue } from '@loom-forge/check';
8
+
9
+ /** O documento: o sistema inteiro. É o que o palco renderiza e o que a análise julga. */
10
+ type Document = readonly ComponentDef[];
11
+ /** Quem falou — o `source` do diagnóstico. */
12
+ declare const DIAGNOSTIC_SOURCE: {
13
+ readonly compile: "compile";
14
+ readonly check: "check";
15
+ };
16
+ interface ComponentServiceOptions {
17
+ /** o `CapabilityProfile` do DEPLOY — o mesmo que o binder executa. Default: `DEFAULT_PROFILE`. */
18
+ profile?: CapabilityProfile;
19
+ /** o catálogo de tipos nomeados — o mesmo que vai ao `buildEnv` e vale como `TypeRef`. */
20
+ types?: TypeCatalog;
21
+ /** o catálogo de contextos do sistema (`nome → TypeRef`) — a raiz `ctx` e o que tipa um `provides`. */
22
+ contexts?: Record<string, TypeRef>;
23
+ /** o que cada componente REGISTRADO emite num evento (`descriptor.meta.emits`). */
24
+ emitsOf?: (type: string, event: string) => Schema | undefined;
25
+ /** a porta de ação do host — de onde sai a assinatura de uma ação que não é update local. */
26
+ actions?: CheckComponentOptions["actions"];
27
+ /** o vocabulário de comando do host — a borda do roteamento, como o `ambient` é a do contexto. */
28
+ commands?: CommandScope;
29
+ }
30
+ interface ComponentService {
31
+ /** o sistema que o documento descreve — o IR, para quem lê grafo e fatos. */
32
+ system(doc: Document): SystemIR;
33
+ /** a montagem do sistema — o que cada componente instala nos próprios slots. */
34
+ mounting(doc: Document): Mounting;
35
+ /** a definição compilada — o artefato que o palco executa —, com o que a compilação disse. */
36
+ compiled(doc: Document, component: string): CompiledComponent | undefined;
37
+ /** tudo o que a compilação e os tipos reclamaram, na coordenada do documento. */
38
+ diagnostics(doc: Document): readonly FieldDiagnostic[];
39
+ /** idem, de UM componente. */
40
+ diagnosticsOf(doc: Document, component: string): readonly FieldDiagnostic[];
41
+ /**
42
+ * O ambiente de tipos de um campo — a porta que o campo de expressão de um inspetor liga.
43
+ *
44
+ * Responde pelo que o campo ENXERGA de onde está: as raízes do componente, o item do `each`
45
+ * em que o nó mora, os `params` de um update, e só `props`/`ctx` num `init`. Responde por
46
+ * um campo vazio e por um que não compilou — o ambiente de um campo é função do que vem
47
+ * antes dele no documento, não do texto dele. `undefined` só quando o caminho não é de um
48
+ * componente do documento.
49
+ */
50
+ envAt(doc: Document, path: DocumentPath): Env | undefined;
51
+ }
52
+ declare class LanguageServiceError extends Error {
53
+ readonly code: "invalid-context-catalog";
54
+ readonly issues: readonly CheckIssue[];
55
+ constructor(code: "invalid-context-catalog", message: string, issues?: readonly CheckIssue[]);
56
+ }
57
+ declare function createComponentService(opts?: ComponentServiceOptions): ComponentService;
58
+
59
+ /** O caminho do `check` → os segmentos dentro do `ComponentDef`. */
60
+ declare function segmentsOf(path: CompiledPath): readonly DocumentSegment[];
61
+ /** Os segmentos dentro do `ComponentDef` → o caminho do `check`. A inversa de `segmentsOf`. */
62
+ declare function pathOf(segments: readonly DocumentSegment[]): CompiledPath;
63
+ /** A coordenada de um campo de `component`, a partir do caminho do `check`. */
64
+ declare const documentPathOf: (component: string, path: CompiledPath) => DocumentPath;
65
+ /** O prefixo de NÓ de um caminho da árvore: `[0, 1, "fallback"]` de `[0, 1, "fallback", "props", "x"]`. */
66
+ declare function nodePrefixOf(path: CompiledPath): CompiledPath;
67
+
68
+ export { type ComponentService, type ComponentServiceOptions, DIAGNOSTIC_SOURCE, type Document, LanguageServiceError, createComponentService, documentPathOf, nodePrefixOf, pathOf, segmentsOf };
package/dist/index.js ADDED
@@ -0,0 +1,173 @@
1
+ import { scopeAt } from '@tslite/checker';
2
+ import { DEFAULT_PROFILE, compile } from '@loom-forge/tslite/compile';
3
+ import { buildSystemIR, compileComponent, mountingOf, compileDefinition, addressOf } from '@loom-forge/forge';
4
+ import { checkContextCatalog, resolveTypeRef, declaredPropsOf, buildEnv, analyzeComponent } from '@loom-forge/check';
5
+ import { TREE } from '@loom-forge/core';
6
+
7
+ // src/service.ts
8
+ function segmentsOf(path) {
9
+ if (typeof path[0] !== "number") return path;
10
+ const out = ["body"];
11
+ let i = 1;
12
+ while (i < path.length) {
13
+ const seg = path[i];
14
+ if (typeof seg === "number") out.push("children", seg);
15
+ else if (TREE[seg] === "node") out.push(seg);
16
+ else break;
17
+ i++;
18
+ }
19
+ return [...out, ...path.slice(i)];
20
+ }
21
+ function pathOf(segments) {
22
+ if (segments[0] !== "body") return segments;
23
+ const out = [0];
24
+ let i = 1;
25
+ while (i < segments.length) {
26
+ const seg = segments[i];
27
+ const next = segments[i + 1];
28
+ if (seg === "children" && typeof next === "number") {
29
+ out.push(next);
30
+ i += 2;
31
+ } else if (typeof seg === "string" && TREE[seg] === "node") {
32
+ out.push(seg);
33
+ i++;
34
+ } else break;
35
+ }
36
+ return [...out, ...segments.slice(i)];
37
+ }
38
+ var documentPathOf = (component, path) => ({ root: component, segments: segmentsOf(path) });
39
+ function nodePrefixOf(path) {
40
+ let end = 1;
41
+ while (end < path.length) {
42
+ const seg = path[end];
43
+ if (typeof seg === "number" || TREE[seg] === "node") end++;
44
+ else break;
45
+ }
46
+ return path.slice(0, end);
47
+ }
48
+
49
+ // src/service.ts
50
+ var DIAGNOSTIC_SOURCE = {
51
+ compile: "compile",
52
+ check: "check"
53
+ };
54
+ var LanguageServiceError = class extends Error {
55
+ constructor(code, message, issues = []) {
56
+ super(message);
57
+ this.code = code;
58
+ this.issues = issues;
59
+ this.name = "LanguageServiceError";
60
+ }
61
+ code;
62
+ issues;
63
+ };
64
+ var rootBlockOf = (program) => program.body;
65
+ function createComponentService(opts = {}) {
66
+ const profile = opts.profile ?? DEFAULT_PROFILE;
67
+ const types = opts.types ?? {};
68
+ const catalogIssues = checkContextCatalog(opts.contexts, types);
69
+ if (catalogIssues.length > 0)
70
+ throw new LanguageServiceError(
71
+ "invalid-context-catalog",
72
+ `The context catalog does not resolve: ${catalogIssues.map((i) => i.address).join(", ")}.`,
73
+ catalogIssues
74
+ );
75
+ const contextSchemas = opts.contexts ? Object.fromEntries(
76
+ Object.entries(opts.contexts).map(([name, ref]) => [
77
+ name,
78
+ resolveTypeRef(ref, types)
79
+ ])
80
+ ) : void 0;
81
+ const translate = (node) => compile(node, { profile });
82
+ const analyses = /* @__PURE__ */ new WeakMap();
83
+ const analyse = (doc) => {
84
+ const hit = analyses.get(doc);
85
+ if (hit) return hit;
86
+ const names = new Set(doc.map((d) => d.component));
87
+ const system = buildSystemIR(
88
+ doc.map((d) => compileComponent(d, { components: names })),
89
+ opts.commands ? { commands: opts.commands } : {}
90
+ );
91
+ const mounting = mountingOf(
92
+ system.components.values(),
93
+ opts.commands ? { host: opts.commands } : {}
94
+ );
95
+ const propsOf = declaredPropsOf(system);
96
+ const entries = /* @__PURE__ */ new Map();
97
+ for (const def of doc) {
98
+ const env = buildEnv(def, {
99
+ types,
100
+ ...contextSchemas ? { contexts: contextSchemas } : {}
101
+ });
102
+ const compiled = compileDefinition(def, translate);
103
+ const analysis = analyzeComponent(def, env, {
104
+ compiled,
105
+ profile,
106
+ types,
107
+ propsOf,
108
+ mounting,
109
+ ...opts.contexts ? { contexts: opts.contexts } : {},
110
+ ...opts.emitsOf ? { emitsOf: opts.emitsOf } : {},
111
+ ...opts.actions ? { actions: opts.actions } : {}
112
+ });
113
+ entries.set(def.component, { def, env, compiled, analysis });
114
+ }
115
+ const result = { system, mounting, entries };
116
+ analyses.set(doc, result);
117
+ return result;
118
+ };
119
+ const fieldDiagnosticsOf = (entry) => [
120
+ ...entry.compiled.diagnostics.map((d) => ({
121
+ path: documentPathOf(entry.def.component, d.path),
122
+ code: d.code,
123
+ message: d.message,
124
+ severity: d.severity,
125
+ source: DIAGNOSTIC_SOURCE.compile,
126
+ ...d.span === void 0 ? {} : { span: d.span }
127
+ })),
128
+ ...entry.analysis.issues.map((i) => ({
129
+ path: documentPathOf(entry.def.component, i.path),
130
+ code: i.code,
131
+ message: i.message,
132
+ severity: i.severity,
133
+ source: DIAGNOSTIC_SOURCE.check,
134
+ ...i.span === void 0 ? {} : { span: i.span }
135
+ }))
136
+ ];
137
+ const envAt = (entry, path) => {
138
+ const { analysis } = entry;
139
+ if (path[0] === "state" && path[2] === "init") return analysis.initEnv;
140
+ const { frames, program } = analysis.lowered;
141
+ let frame;
142
+ if (typeof path[0] === "number") {
143
+ const node = nodePrefixOf(path);
144
+ const own = path[node.length] !== "each" ? node : node.slice(0, -1);
145
+ frame = own.length > 0 ? frames.get(addressOf(own)) : void 0;
146
+ } else if (path[0] === "updates" && typeof path[1] === "string") {
147
+ frame = frames.get(addressOf(["updates", path[1]]));
148
+ }
149
+ const scope = scopeAt(
150
+ analysis.body.overlay,
151
+ frame ?? rootBlockOf(program)
152
+ );
153
+ return scope ? { ...entry.env, scope } : entry.env;
154
+ };
155
+ return {
156
+ system: (doc) => analyse(doc).system,
157
+ mounting: (doc) => analyse(doc).mounting,
158
+ compiled: (doc, component) => analyse(doc).entries.get(component)?.compiled,
159
+ diagnostics: (doc) => [...analyse(doc).entries.values()].flatMap(fieldDiagnosticsOf),
160
+ diagnosticsOf: (doc, component) => {
161
+ const entry = analyse(doc).entries.get(component);
162
+ return entry ? fieldDiagnosticsOf(entry) : [];
163
+ },
164
+ envAt: (doc, path) => {
165
+ const entry = analyse(doc).entries.get(path.root);
166
+ return entry ? envAt(entry, pathOf(path.segments)) : void 0;
167
+ }
168
+ };
169
+ }
170
+
171
+ export { DIAGNOSTIC_SOURCE, LanguageServiceError, createComponentService, documentPathOf, nodePrefixOf, pathOf, segmentsOf };
172
+ //# sourceMappingURL=index.js.map
173
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/path.ts","../src/service.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { TREE } from \"@loom-forge/core\";\nimport type { CompiledPath } from \"@loom-forge/forge\";\nimport type { DocumentPath, DocumentSegment } from \"@tslite/language-service\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n A COORDENADA — o caminho REAL dentro do `ComponentDef`, como lista.\n\n O `check` fala o caminho da compilação: na árvore, a raiz é o nó `0` e um número é um\n filho (`[0, 1, \"props\", \"title\"]`); fora dela, o caminho já é o do slot\n (`[\"updates\", \"somar\", \"set\", \"n\"]`). O documento que um inspetor edita é o `ComponentDef`\n inteiro, e o campo mora em `[\"body\", \"children\", 1, \"props\", \"title\"]`. É esta a\n coordenada do contrato (`DocumentPath` do `@tslite/language-service`): `valueAt(def,\n segments)` e `fieldText(def, segments)` andam o objeto sem tradução nenhuma.\n\n As duas formas são LISTAS, e a tradução é pela tabela `TREE` do core — nunca por parse\n de string. O endereço (`#0.1.props.title`) é exibição, e ninguém o lê de volta.\n ────────────────────────────────────────────────────────────────────────────── */\n\n/** O caminho do `check` → os segmentos dentro do `ComponentDef`. */\nexport function segmentsOf(path: CompiledPath): readonly DocumentSegment[] {\n if (typeof path[0] !== \"number\") return path; // fora da árvore: já é o caminho real\n const out: DocumentSegment[] = [\"body\"];\n let i = 1;\n // dentro da árvore: um número é um filho; um campo `tree` de nó único desce por ele; o\n // primeiro campo de outro tipo é o slot, e dali em diante o caminho é o do valor\n while (i < path.length) {\n const seg = path[i]!;\n if (typeof seg === \"number\") out.push(\"children\", seg);\n else if (TREE[seg] === \"node\") out.push(seg);\n else break;\n i++;\n }\n return [...out, ...path.slice(i)];\n}\n\n/** Os segmentos dentro do `ComponentDef` → o caminho do `check`. A inversa de `segmentsOf`. */\nexport function pathOf(segments: readonly DocumentSegment[]): CompiledPath {\n if (segments[0] !== \"body\") return segments; // fora da árvore\n const out: (string | number)[] = [0];\n let i = 1;\n while (i < segments.length) {\n const seg = segments[i]!;\n const next = segments[i + 1];\n if (seg === \"children\" && typeof next === \"number\") {\n out.push(next);\n i += 2;\n } else if (typeof seg === \"string\" && TREE[seg] === \"node\") {\n out.push(seg);\n i++;\n } else break;\n }\n return [...out, ...segments.slice(i)];\n}\n\n/** A coordenada de um campo de `component`, a partir do caminho do `check`. */\nexport const documentPathOf = (\n component: string,\n path: CompiledPath,\n): DocumentPath => ({ root: component, segments: segmentsOf(path) });\n\n/** O prefixo de NÓ de um caminho da árvore: `[0, 1, \"fallback\"]` de `[0, 1, \"fallback\", \"props\", \"x\"]`. */\nexport function nodePrefixOf(path: CompiledPath): CompiledPath {\n let end = 1;\n while (end < path.length) {\n const seg = path[end]!;\n if (typeof seg === \"number\" || TREE[seg] === \"node\") end++;\n else break;\n }\n return path.slice(0, end);\n}\n","import type { Env } from \"@tslite/checker\";\nimport { scopeAt } from \"@tslite/checker\";\nimport type { Schema } from \"@tslite/type-core\";\nimport type { CapabilityProfile } from \"@tslite/capability\";\nimport type { DocumentPath, FieldDiagnostic } from \"@tslite/language-service\";\nimport type { CommandScope, DefNode } from \"@loom-forge/core\";\nimport {\n compile,\n DEFAULT_PROFILE,\n type CompileResult,\n} from \"@loom-forge/tslite/compile\";\nimport {\n addressOf,\n buildSystemIR,\n compileComponent,\n compileDefinition,\n mountingOf,\n type CompiledPath,\n type ComponentDef,\n type Mounting,\n type SystemIR,\n type TypeRef,\n} from \"@loom-forge/forge\";\nimport {\n analyzeComponent,\n buildEnv,\n checkContextCatalog,\n declaredPropsOf,\n resolveTypeRef,\n type CheckComponentOptions,\n type CheckIssue,\n type CompiledComponent,\n type ComponentAnalysis,\n type TypeCatalog,\n} from \"@loom-forge/check\";\nimport { documentPathOf, nodePrefixOf, pathOf } from \"./path\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n O SERVIÇO DE LINGUAGEM — a tese é a do `@node-actions/language-service`: ele não é\n dono de regra nenhuma; é dono de saber a quem perguntar.\n\n COMPILAÇÃO `compileDefinition` + `compile` sintaxe, ilha vazia, o que o deploy recusa\n TIPOS `analyzeComponent` do `check` tipo, escopo, call-site, declaração\n\n O que ele acrescenta é o que nenhum dos dois responde: a resposta na COORDENADA que o\n autor enxerga (o `DocumentPath` do contrato, o caminho real dentro do `ComponentDef`),\n e o AMBIENTE de um campo — que nomes ele enxerga, com que tipo —, projetado da mesma\n análise que o tipa, e não recalculado por campo.\n\n O MUNDO entra na construção (o deploy, os catálogos, o kit, o host); o DOCUMENTO — o\n sistema inteiro, a lista de definições — é argumento de todo método, e a análise é\n memoizada pela identidade dele. Documento como estado do serviço é a classe de bug \"o\n serviço está uma versão atrás\", e ela deixa de existir por construção.\n ────────────────────────────────────────────────────────────────────────────── */\n\n/** O documento: o sistema inteiro. É o que o palco renderiza e o que a análise julga. */\nexport type Document = readonly ComponentDef[];\n\n/** Quem falou — o `source` do diagnóstico. */\nexport const DIAGNOSTIC_SOURCE = {\n compile: \"compile\",\n check: \"check\",\n} as const;\n\nexport interface ComponentServiceOptions {\n /** o `CapabilityProfile` do DEPLOY — o mesmo que o binder executa. Default: `DEFAULT_PROFILE`. */\n profile?: CapabilityProfile;\n /** o catálogo de tipos nomeados — o mesmo que vai ao `buildEnv` e vale como `TypeRef`. */\n types?: TypeCatalog;\n /** o catálogo de contextos do sistema (`nome → TypeRef`) — a raiz `ctx` e o que tipa um `provides`. */\n contexts?: Record<string, TypeRef>;\n /** o que cada componente REGISTRADO emite num evento (`descriptor.meta.emits`). */\n emitsOf?: (type: string, event: string) => Schema | undefined;\n /** a porta de ação do host — de onde sai a assinatura de uma ação que não é update local. */\n actions?: CheckComponentOptions[\"actions\"];\n /** o vocabulário de comando do host — a borda do roteamento, como o `ambient` é a do contexto. */\n commands?: CommandScope;\n}\n\nexport interface ComponentService {\n /** o sistema que o documento descreve — o IR, para quem lê grafo e fatos. */\n system(doc: Document): SystemIR;\n /** a montagem do sistema — o que cada componente instala nos próprios slots. */\n mounting(doc: Document): Mounting;\n /** a definição compilada — o artefato que o palco executa —, com o que a compilação disse. */\n compiled(doc: Document, component: string): CompiledComponent | undefined;\n /** tudo o que a compilação e os tipos reclamaram, na coordenada do documento. */\n diagnostics(doc: Document): readonly FieldDiagnostic[];\n /** idem, de UM componente. */\n diagnosticsOf(doc: Document, component: string): readonly FieldDiagnostic[];\n /**\n * O ambiente de tipos de um campo — a porta que o campo de expressão de um inspetor liga.\n *\n * Responde pelo que o campo ENXERGA de onde está: as raízes do componente, o item do `each`\n * em que o nó mora, os `params` de um update, e só `props`/`ctx` num `init`. Responde por\n * um campo vazio e por um que não compilou — o ambiente de um campo é função do que vem\n * antes dele no documento, não do texto dele. `undefined` só quando o caminho não é de um\n * componente do documento.\n */\n envAt(doc: Document, path: DocumentPath): Env | undefined;\n}\n\nexport class LanguageServiceError extends Error {\n constructor(\n readonly code: \"invalid-context-catalog\",\n message: string,\n readonly issues: readonly CheckIssue[] = [],\n ) {\n super(message);\n this.name = \"LanguageServiceError\";\n }\n}\n\n/** a análise de UM componente do documento, com o que a alimenta. */\ninterface Entry {\n readonly def: ComponentDef;\n readonly env: Env;\n readonly compiled: CompiledComponent;\n readonly analysis: ComponentAnalysis;\n}\n\n/** a análise de UM documento — memoizada pela identidade dele. */\ninterface DocAnalysis {\n readonly system: SystemIR;\n readonly mounting: Mounting;\n readonly entries: ReadonlyMap<string, Entry>;\n}\n\n/** o bloco raiz do corpo baixado — a arrow que o lowering devolve, por dentro. */\nconst rootBlockOf = (program: unknown): object =>\n (program as { body: object }).body;\n\nexport function createComponentService(\n opts: ComponentServiceOptions = {},\n): ComponentService {\n const profile = opts.profile ?? DEFAULT_PROFILE;\n const types = opts.types ?? {};\n\n // o catálogo de contextos é do MUNDO, e um contrato que não resolve é erro de quem monta o\n // sistema — não diagnóstico do programa. Recusado aqui, uma vez, como o `createBoundary`.\n const catalogIssues = checkContextCatalog(opts.contexts, types);\n if (catalogIssues.length > 0)\n throw new LanguageServiceError(\n \"invalid-context-catalog\",\n `The context catalog does not resolve: ${catalogIssues.map((i) => i.address).join(\", \")}.`,\n catalogIssues,\n );\n const contextSchemas = opts.contexts\n ? Object.fromEntries(\n Object.entries(opts.contexts).map(([name, ref]) => [\n name,\n resolveTypeRef(ref, types),\n ]),\n )\n : undefined;\n\n /** o tradutor deste deploy: ESTÁVEL, porque a memória do `compileDefinition` é por tradutor. */\n const translate = (node: DefNode): CompileResult =>\n compile(node, { profile });\n\n const analyses = new WeakMap<Document, DocAnalysis>();\n\n const analyse = (doc: Document): DocAnalysis => {\n const hit = analyses.get(doc);\n if (hit) return hit;\n\n const names = new Set(doc.map((d) => d.component));\n const system = buildSystemIR(\n doc.map((d) => compileComponent(d, { components: names })),\n opts.commands ? { commands: opts.commands } : {},\n );\n const mounting = mountingOf(\n system.components.values(),\n opts.commands ? { host: opts.commands } : {},\n );\n const propsOf = declaredPropsOf(system);\n\n const entries = new Map<string, Entry>();\n for (const def of doc) {\n const env = buildEnv(def, {\n types,\n ...(contextSchemas ? { contexts: contextSchemas } : {}),\n });\n // UMA compilação por definição, com o profile do deploy: o que o bind recusaria aparece\n // no campo. O artefato vai ao `check`, que tipa as ilhas em memória dele — e não repete\n // o que a compilação disse.\n const compiled = compileDefinition(def, translate);\n const analysis = analyzeComponent(def, env, {\n compiled,\n profile,\n types,\n propsOf,\n mounting,\n ...(opts.contexts ? { contexts: opts.contexts } : {}),\n ...(opts.emitsOf ? { emitsOf: opts.emitsOf } : {}),\n ...(opts.actions ? { actions: opts.actions } : {}),\n });\n entries.set(def.component, { def, env, compiled, analysis });\n }\n\n const result: DocAnalysis = { system, mounting, entries };\n analyses.set(doc, result);\n return result;\n };\n\n const fieldDiagnosticsOf = (entry: Entry): FieldDiagnostic[] => [\n ...entry.compiled.diagnostics.map((d) => ({\n path: documentPathOf(entry.def.component, d.path),\n code: d.code,\n message: d.message,\n severity: d.severity,\n source: DIAGNOSTIC_SOURCE.compile,\n ...(d.span === undefined ? {} : { span: d.span }),\n })),\n ...entry.analysis.issues.map((i) => ({\n path: documentPathOf(entry.def.component, i.path),\n code: i.code,\n message: i.message,\n severity: i.severity,\n source: DIAGNOSTIC_SOURCE.check,\n ...(i.span === undefined ? {} : { span: i.span }),\n })),\n ];\n\n const envAt = (entry: Entry, path: CompiledPath): Env => {\n const { analysis } = entry;\n // o `init` semeia o estado: outro escopo, outro `env`\n if (path[0] === \"state\" && path[2] === \"init\") return analysis.initEnv;\n\n const { frames, program } = analysis.lowered;\n let frame: object | undefined;\n if (typeof path[0] === \"number\") {\n // o campo de um nó enxerga o bloco do nó — menos o `each`, que é avaliado FORA do laço\n // que ele mesmo abre: o dele é o do pai\n const node = nodePrefixOf(path);\n const own = path[node.length] !== \"each\" ? node : node.slice(0, -1);\n frame = own.length > 0 ? frames.get(addressOf(own)) : undefined;\n } else if (path[0] === \"updates\" && typeof path[1] === \"string\") {\n frame = frames.get(addressOf([\"updates\", path[1]]));\n }\n const scope = scopeAt(\n analysis.body.overlay,\n (frame ?? rootBlockOf(program)) as never,\n );\n return scope ? { ...entry.env, scope } : entry.env;\n };\n\n return {\n system: (doc) => analyse(doc).system,\n mounting: (doc) => analyse(doc).mounting,\n compiled: (doc, component) => analyse(doc).entries.get(component)?.compiled,\n diagnostics: (doc) =>\n [...analyse(doc).entries.values()].flatMap(fieldDiagnosticsOf),\n diagnosticsOf: (doc, component) => {\n const entry = analyse(doc).entries.get(component);\n return entry ? fieldDiagnosticsOf(entry) : [];\n },\n envAt: (doc, path) => {\n const entry = analyse(doc).entries.get(path.root);\n return entry ? envAt(entry, pathOf(path.segments)) : undefined;\n },\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@loom-forge/language-service",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "forge · o serviço de linguagem (dev/CI): os diagnósticos da compilação e dos tipos na coordenada do documento (DocumentPath), e o ambiente de tipos de um campo — a projeção da análise que um inspetor ou um canvas consome.",
6
+ "keywords": [
7
+ "language-service",
8
+ "diagnostics",
9
+ "lsp",
10
+ "json",
11
+ "dsl",
12
+ "tslite"
13
+ ],
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Anderson D. Rosa",
17
+ "url": "https://github.com/andersondrosa"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/andersondrosa/loom-forge.git",
22
+ "directory": "packages/language-service"
23
+ },
24
+ "homepage": "https://github.com/andersondrosa/loom-forge/tree/main/packages/language-service#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/andersondrosa/loom-forge/issues"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "sideEffects": false,
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "dependencies": {
42
+ "@tslite/capability": "^0.6.0",
43
+ "@tslite/checker": "^0.12.0",
44
+ "@tslite/language-service": "^0.4.0",
45
+ "@tslite/type-core": "^0.6.0",
46
+ "@loom-forge/tslite": "0.1.0",
47
+ "@loom-forge/core": "0.1.0",
48
+ "@loom-forge/forge": "0.1.0",
49
+ "@loom-forge/check": "0.1.0"
50
+ },
51
+ "devDependencies": {
52
+ "@tslite/parser": "^1.4.0",
53
+ "tsup": "^8.0.0",
54
+ "typescript": "^5.9.3"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "dev": "tsup --watch",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest",
61
+ "typecheck": "tsc --noEmit",
62
+ "clean": "rm -rf dist"
63
+ }
64
+ }