@hatchingpoint/point 0.0.3 → 0.0.6

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.
@@ -0,0 +1,230 @@
1
+ import type { PointSourceSpan } from "../core/ast.ts";
2
+
3
+ export interface PointSemanticProgram {
4
+ kind: "semanticProgram";
5
+ module?: string;
6
+ uses: PointSemanticUseDeclaration[];
7
+ declarations: PointSemanticDeclaration[];
8
+ span?: PointSourceSpan;
9
+ }
10
+
11
+ export interface PointSemanticUseDeclaration {
12
+ kind: "use";
13
+ moduleName: string;
14
+ from?: string;
15
+ span?: PointSourceSpan;
16
+ }
17
+
18
+ export type PointSemanticDeclaration =
19
+ | PointSemanticRecordDeclaration
20
+ | PointSemanticCalculationDeclaration
21
+ | PointSemanticRuleDeclaration
22
+ | PointSemanticLabelDeclaration
23
+ | PointSemanticExternalDeclaration
24
+ | PointSemanticActionDeclaration
25
+ | PointSemanticPolicyDeclaration
26
+ | PointSemanticViewDeclaration
27
+ | PointSemanticRouteDeclaration
28
+ | PointSemanticWorkflowDeclaration
29
+ | PointSemanticCommandDeclaration;
30
+
31
+ export interface PointSemanticRecordDeclaration {
32
+ kind: "record";
33
+ name: string;
34
+ fields: PointSemanticField[];
35
+ span?: PointSourceSpan;
36
+ }
37
+
38
+ export interface PointSemanticField {
39
+ label: string;
40
+ type: PointSemanticTypeExpression;
41
+ span?: PointSourceSpan;
42
+ }
43
+
44
+ export interface PointSemanticBinding {
45
+ label: string;
46
+ type: PointSemanticTypeExpression;
47
+ span?: PointSourceSpan;
48
+ }
49
+
50
+ export interface PointSemanticOutputBinding {
51
+ name: string;
52
+ type: PointSemanticTypeExpression;
53
+ span?: PointSourceSpan;
54
+ }
55
+
56
+ export interface PointSemanticCalculationDeclaration {
57
+ kind: "calculation";
58
+ name: string;
59
+ inputs: PointSemanticBinding[];
60
+ output: PointSemanticOutputBinding;
61
+ body: PointSemanticCalculationStatement[];
62
+ span?: PointSourceSpan;
63
+ }
64
+
65
+ export interface PointSemanticRuleDeclaration {
66
+ kind: "rule";
67
+ name: string;
68
+ inputs: PointSemanticBinding[];
69
+ output: PointSemanticOutputBinding;
70
+ body: PointSemanticRuleStatement[];
71
+ span?: PointSourceSpan;
72
+ }
73
+
74
+ export interface PointSemanticLabelDeclaration {
75
+ kind: "label";
76
+ name: string;
77
+ inputs: PointSemanticBinding[];
78
+ output: PointSemanticOutputBinding;
79
+ body: PointSemanticLabelStatement[];
80
+ span?: PointSourceSpan;
81
+ }
82
+
83
+ export interface PointSemanticExternalDeclaration {
84
+ kind: "external";
85
+ name: string;
86
+ functions: PointSemanticExternalFunction[];
87
+ span?: PointSourceSpan;
88
+ }
89
+
90
+ export interface PointSemanticExternalFunction {
91
+ label: string;
92
+ params: PointSemanticBinding[];
93
+ returnType: PointSemanticTypeExpression;
94
+ from: string;
95
+ importAs?: string;
96
+ span?: PointSourceSpan;
97
+ }
98
+
99
+ export interface PointSemanticActionDeclaration {
100
+ kind: "action";
101
+ name: string;
102
+ inputs: PointSemanticBinding[];
103
+ output: PointSemanticOutputBinding;
104
+ touches: string[];
105
+ body: PointSemanticActionStatement[];
106
+ span?: PointSourceSpan;
107
+ }
108
+
109
+ export interface PointSemanticPolicyDeclaration {
110
+ kind: "policy";
111
+ name: string;
112
+ inputs: PointSemanticBinding[];
113
+ body: PointSemanticPolicyStatement[];
114
+ span?: PointSourceSpan;
115
+ }
116
+
117
+ export interface PointSemanticViewDeclaration {
118
+ kind: "view";
119
+ name: string;
120
+ inputs: PointSemanticBinding[];
121
+ output: PointSemanticOutputBinding;
122
+ body: PointSemanticViewStatement[];
123
+ span?: PointSourceSpan;
124
+ }
125
+
126
+ export interface PointSemanticRouteDeclaration {
127
+ kind: "route";
128
+ name: string;
129
+ method: string;
130
+ path: string;
131
+ inputs: PointSemanticBinding[];
132
+ output: PointSemanticOutputBinding;
133
+ body: PointSemanticRouteStatement[];
134
+ span?: PointSourceSpan;
135
+ }
136
+
137
+ export interface PointSemanticWorkflowDeclaration {
138
+ kind: "workflow";
139
+ name: string;
140
+ inputs: PointSemanticBinding[];
141
+ output: PointSemanticOutputBinding;
142
+ body: PointSemanticWorkflowStatement[];
143
+ span?: PointSourceSpan;
144
+ }
145
+
146
+ export interface PointSemanticCommandDeclaration {
147
+ kind: "command";
148
+ name: string;
149
+ inputs: PointSemanticBinding[];
150
+ output: PointSemanticOutputBinding;
151
+ body: PointSemanticCommandStatement[];
152
+ span?: PointSourceSpan;
153
+ }
154
+
155
+ export type PointSemanticCalculationStatement =
156
+ | { kind: "assignIs"; name: string; value: PointSemanticExpression; span?: PointSourceSpan }
157
+ | { kind: "startsAt"; name: string; value: PointSemanticExpression; span?: PointSourceSpan }
158
+ | { kind: "startsAs"; name: string; value: PointSemanticExpression; span?: PointSourceSpan }
159
+ | { kind: "forEach"; item: string; iterable: PointSemanticExpression; body: PointSemanticMutationStatement[]; span?: PointSourceSpan }
160
+ | PointSemanticMutationStatement
161
+ | { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan };
162
+
163
+ export type PointSemanticRuleStatement =
164
+ | { kind: "startsAt"; name: string; value: PointSemanticExpression; span?: PointSourceSpan }
165
+ | { kind: "addWhen"; amount: PointSemanticExpression; condition: PointSemanticExpression; span?: PointSourceSpan }
166
+ | { kind: "forEach"; item: string; iterable: PointSemanticExpression; body: PointSemanticMutationStatement[]; span?: PointSourceSpan }
167
+ | PointSemanticMutationStatement
168
+ | { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan };
169
+
170
+ export type PointSemanticLabelStatement =
171
+ | { kind: "whenReturn"; condition: PointSemanticExpression; value: PointSemanticExpression; span?: PointSourceSpan }
172
+ | { kind: "otherwiseReturn"; value: PointSemanticExpression; span?: PointSourceSpan };
173
+
174
+ export type PointSemanticActionStatement =
175
+ | { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan }
176
+ | { kind: "expression"; value: PointSemanticExpression; span?: PointSourceSpan };
177
+
178
+ export type PointSemanticPolicyStatement =
179
+ | { kind: "allow"; condition: PointSemanticExpression; span?: PointSourceSpan }
180
+ | { kind: "deny"; condition: PointSemanticExpression; span?: PointSourceSpan }
181
+ | { kind: "require"; condition: PointSemanticExpression; span?: PointSourceSpan };
182
+
183
+ export type PointSemanticViewStatement =
184
+ | { kind: "render"; value: PointSemanticExpression; span?: PointSourceSpan }
185
+ | { kind: "whenRender"; condition: PointSemanticExpression; value: PointSemanticExpression; span?: PointSourceSpan };
186
+
187
+ export type PointSemanticRouteStatement = { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan };
188
+
189
+ export type PointSemanticWorkflowStatement =
190
+ | { kind: "step"; name: string; value: PointSemanticExpression; span?: PointSourceSpan }
191
+ | { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan };
192
+
193
+ export type PointSemanticCommandStatement = { kind: "return"; value: PointSemanticExpression; span?: PointSourceSpan };
194
+
195
+ export type PointSemanticMutationStatement =
196
+ | { kind: "addTo"; amount: PointSemanticExpression; target: string; span?: PointSourceSpan }
197
+ | { kind: "subtractFrom"; amount: PointSemanticExpression; target: string; span?: PointSourceSpan }
198
+ | { kind: "setTo"; target: string; value: PointSemanticExpression; span?: PointSourceSpan };
199
+
200
+ export interface PointSemanticTypeExpression {
201
+ kind: "typeRef";
202
+ name: string;
203
+ args: PointSemanticTypeExpression[];
204
+ span?: PointSourceSpan;
205
+ }
206
+
207
+ export type PointSemanticExpression =
208
+ | { kind: "literal"; value: string | number | boolean | null; span?: PointSourceSpan }
209
+ | { kind: "name"; label: string; span?: PointSourceSpan }
210
+ | { kind: "property"; target: PointSemanticExpression; label: string; span?: PointSourceSpan }
211
+ | { kind: "list"; items: PointSemanticExpression[]; span?: PointSourceSpan }
212
+ | { kind: "record"; fields: PointSemanticRecordLiteralField[]; span?: PointSourceSpan }
213
+ | { kind: "call"; callee: string; args: PointSemanticExpression[]; span?: PointSourceSpan }
214
+ | { kind: "await"; value: PointSemanticExpression; span?: PointSourceSpan }
215
+ | { kind: "error"; message: string; span?: PointSourceSpan }
216
+ | {
217
+ kind: "binary";
218
+ operator: PointSemanticBinaryOperator;
219
+ left: PointSemanticExpression;
220
+ right: PointSemanticExpression;
221
+ span?: PointSourceSpan;
222
+ };
223
+
224
+ export interface PointSemanticRecordLiteralField {
225
+ label: string;
226
+ value: PointSemanticExpression;
227
+ span?: PointSourceSpan;
228
+ }
229
+
230
+ export type PointSemanticBinaryOperator = "+" | "-" | "*" | "/" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "and" | "or";
@@ -0,0 +1,51 @@
1
+ const CALLABLE_KEYWORDS = [
2
+ "calculation",
3
+ "rule",
4
+ "label",
5
+ "action",
6
+ "view",
7
+ "route",
8
+ "workflow",
9
+ "command",
10
+ ] as const;
11
+
12
+ export function collectSemanticCallables(source: string): string[] {
13
+ const callables = new Set<string>();
14
+ const lines = source.split(/\r?\n/);
15
+ let index = 0;
16
+ while (index < lines.length) {
17
+ const trimmed = (lines[index] ?? "").trim();
18
+ if (trimmed.startsWith("external ")) {
19
+ const body = collectBody(lines, index + 1);
20
+ for (const line of body.lines) {
21
+ const match = line.match(/^(.+)\(/);
22
+ if (match) callables.add(match[1]?.trim() ?? "");
23
+ }
24
+ index = body.next;
25
+ continue;
26
+ }
27
+ for (const keyword of CALLABLE_KEYWORDS) {
28
+ if (trimmed.startsWith(`${keyword} `)) {
29
+ callables.add(trimmed.slice(keyword.length + 1).trim());
30
+ }
31
+ }
32
+ index += 1;
33
+ }
34
+ return [...callables];
35
+ }
36
+
37
+ function collectBody(lines: string[], start: number): { lines: string[]; next: number } {
38
+ const body: string[] = [];
39
+ let index = start;
40
+ for (; index < lines.length; index += 1) {
41
+ const trimmed = (lines[index] ?? "").trim();
42
+ if (!trimmed) continue;
43
+ if (isTopLevel(trimmed)) break;
44
+ body.push(trimmed);
45
+ }
46
+ return { lines: body, next: index };
47
+ }
48
+
49
+ function isTopLevel(line: string): boolean {
50
+ return /^(module|use|record|calculation|rule|label|external|action|policy|view|route|workflow|command)\s+/.test(line);
51
+ }
@@ -0,0 +1,347 @@
1
+ import type { PointCoreProgram, PointSourceSpan } from "../core/ast.ts";
2
+ import type { PointCoreDiagnostic } from "../core/check.ts";
3
+ import { mapDiagnosticsToSemanticRefs } from "../core/context.ts";
4
+ import type {
5
+ PointSemanticBinding,
6
+ PointSemanticDeclaration,
7
+ PointSemanticExpression,
8
+ PointSemanticOutputBinding,
9
+ PointSemanticProgram,
10
+ PointSemanticTypeExpression,
11
+ } from "./ast.ts";
12
+
13
+ function formatSemanticType(type: PointSemanticTypeExpression): string {
14
+ if (type.args.length === 0) return type.name;
15
+ return `${type.name}<${type.args.map(formatSemanticType).join(", ")}>`;
16
+ }
17
+
18
+ export type PointSemanticSymbolKind =
19
+ | "module"
20
+ | "use"
21
+ | "record"
22
+ | "field"
23
+ | "calculation"
24
+ | "rule"
25
+ | "label"
26
+ | "external"
27
+ | "action"
28
+ | "policy"
29
+ | "view"
30
+ | "route"
31
+ | "workflow"
32
+ | "command"
33
+ | "param";
34
+
35
+ export interface PointSemanticSymbol {
36
+ ref: string;
37
+ path: string;
38
+ kind: PointSemanticSymbolKind;
39
+ name: string;
40
+ module: string;
41
+ type?: string;
42
+ from?: string;
43
+ effects?: string[];
44
+ span: PointSourceSpan | null;
45
+ }
46
+
47
+ export interface PointSemanticIndex {
48
+ schemaVersion: "point.semantic.index.v1";
49
+ module: string;
50
+ refs: PointSemanticSymbol[];
51
+ }
52
+
53
+ export interface PointSemanticExplanation {
54
+ schemaVersion: "point.semantic.explain.v1";
55
+ ref: string;
56
+ found: boolean;
57
+ symbol?: PointSemanticSymbol;
58
+ relatedRefs: string[];
59
+ summary: string;
60
+ }
61
+
62
+ export function createSemanticIndex(program: PointSemanticProgram): PointSemanticIndex {
63
+ const moduleName = program.module ?? "anonymous";
64
+ const refs: PointSemanticSymbol[] = [
65
+ {
66
+ ref: semanticRefFor(moduleName, "module"),
67
+ path: "module",
68
+ kind: "module",
69
+ name: moduleName,
70
+ module: moduleName,
71
+ span: program.span ?? null,
72
+ },
73
+ ...program.uses.map((use) => ({
74
+ ref: semanticRefFor(moduleName, `use.${use.moduleName}`),
75
+ path: `use.${use.moduleName}`,
76
+ kind: "use" as const,
77
+ name: use.moduleName,
78
+ module: moduleName,
79
+ from: use.from,
80
+ span: use.span ?? null,
81
+ })),
82
+ ];
83
+ for (const declaration of program.declarations) {
84
+ refs.push(...symbolsForDeclaration(moduleName, declaration));
85
+ }
86
+ return { schemaVersion: "point.semantic.index.v1", module: moduleName, refs };
87
+ }
88
+
89
+ export function explainSemanticRef(program: PointSemanticProgram, ref: string): PointSemanticExplanation {
90
+ const index = createSemanticIndex(program);
91
+ const symbol = index.refs.find((candidate) => candidate.ref === ref);
92
+ if (!symbol) {
93
+ return {
94
+ schemaVersion: "point.semantic.explain.v1",
95
+ ref,
96
+ found: false,
97
+ relatedRefs: [],
98
+ summary: `No Point symbol found for ${ref}.`,
99
+ };
100
+ }
101
+ const relatedRefs = relatedRefsFor(symbol, index);
102
+ return {
103
+ schemaVersion: "point.semantic.explain.v1",
104
+ ref,
105
+ found: true,
106
+ symbol,
107
+ relatedRefs,
108
+ summary: summaryFor(symbol),
109
+ };
110
+ }
111
+
112
+ export function mapPublicDiagnostics(program: PointCoreProgram, diagnostics: PointCoreDiagnostic[]): PointCoreDiagnostic[] {
113
+ const mapped = mapDiagnosticsToSemanticRefs(program, diagnostics);
114
+ if (!program.semanticSource) return mapped;
115
+ return mapped.map((diagnostic) => ({
116
+ ...diagnostic,
117
+ span: diagnostic.span ?? semanticSpanForDiagnosticPath(program.semanticSource!, program, diagnostic.path),
118
+ }));
119
+ }
120
+
121
+ function symbolsForDeclaration(moduleName: string, declaration: PointSemanticDeclaration): PointSemanticSymbol[] {
122
+ if (declaration.kind === "record") {
123
+ const recordPath = `record.${declaration.name}`;
124
+ return [
125
+ {
126
+ ref: semanticRefFor(moduleName, recordPath),
127
+ path: recordPath,
128
+ kind: "record",
129
+ name: declaration.name,
130
+ module: moduleName,
131
+ span: declaration.span ?? null,
132
+ },
133
+ ...declaration.fields.map((field) => ({
134
+ ref: semanticRefFor(moduleName, `${recordPath}.field.${field.label}`),
135
+ path: `${recordPath}.field.${field.label}`,
136
+ kind: "field" as const,
137
+ name: field.label,
138
+ module: moduleName,
139
+ type: formatSemanticType(field.type),
140
+ span: field.span ?? null,
141
+ })),
142
+ ];
143
+ }
144
+ if (declaration.kind === "external") {
145
+ return declaration.functions.flatMap((fn) => {
146
+ const path = `external.${fn.label}`;
147
+ return [
148
+ {
149
+ ref: semanticRefFor(moduleName, path),
150
+ path,
151
+ kind: "external" as const,
152
+ name: fn.label,
153
+ module: moduleName,
154
+ type: formatSemanticType(fn.returnType),
155
+ from: fn.from,
156
+ span: fn.span ?? null,
157
+ },
158
+ ...bindingSymbols(moduleName, path, fn.params),
159
+ ];
160
+ });
161
+ }
162
+ const callable = callableDeclaration(declaration);
163
+ if (!callable) return [];
164
+ const path = `${callable.kind}.${callable.name}`;
165
+ return [
166
+ {
167
+ ref: semanticRefFor(moduleName, path),
168
+ path,
169
+ kind: callable.kind,
170
+ name: callable.name,
171
+ module: moduleName,
172
+ type: formatSemanticType(callable.output.type),
173
+ effects: callable.effects,
174
+ span: callable.declaration.span ?? null,
175
+ },
176
+ ...bindingSymbols(moduleName, path, callable.inputs),
177
+ {
178
+ ref: semanticRefFor(moduleName, `${path}.output.${callable.output.name}`),
179
+ path: `${path}.output.${callable.output.name}`,
180
+ kind: "param",
181
+ name: callable.output.name,
182
+ module: moduleName,
183
+ type: formatSemanticType(callable.output.type),
184
+ span: callable.output.span ?? null,
185
+ },
186
+ ];
187
+ }
188
+
189
+ function callableDeclaration(declaration: PointSemanticDeclaration):
190
+ | {
191
+ kind: Exclude<PointSemanticSymbolKind, "module" | "use" | "record" | "field" | "external" | "param">;
192
+ name: string;
193
+ inputs: PointSemanticBinding[];
194
+ output: PointSemanticOutputBinding;
195
+ effects?: string[];
196
+ declaration: PointSemanticDeclaration;
197
+ }
198
+ | null {
199
+ if (declaration.kind === "calculation" || declaration.kind === "rule" || declaration.kind === "label") {
200
+ return { kind: declaration.kind, name: declaration.name, inputs: declaration.inputs, output: declaration.output, declaration };
201
+ }
202
+ if (declaration.kind === "action") {
203
+ return {
204
+ kind: "action",
205
+ name: declaration.name,
206
+ inputs: declaration.inputs,
207
+ output: declaration.output,
208
+ effects: declaration.touches,
209
+ declaration,
210
+ };
211
+ }
212
+ if (declaration.kind === "policy") {
213
+ return { kind: "policy", name: declaration.name, inputs: declaration.inputs, output: { name: "allowed", type: boolType() }, declaration };
214
+ }
215
+ if (declaration.kind === "view" || declaration.kind === "route" || declaration.kind === "workflow" || declaration.kind === "command") {
216
+ return { kind: declaration.kind, name: declaration.name, inputs: declaration.inputs, output: declaration.output, declaration };
217
+ }
218
+ return null;
219
+ }
220
+
221
+ function bindingSymbols(moduleName: string, ownerPath: string, bindings: PointSemanticBinding[]): PointSemanticSymbol[] {
222
+ return bindings.map((binding) => ({
223
+ ref: semanticRefFor(moduleName, `${ownerPath}.input.${binding.label}`),
224
+ path: `${ownerPath}.input.${binding.label}`,
225
+ kind: "param" as const,
226
+ name: binding.label,
227
+ module: moduleName,
228
+ type: formatSemanticType(binding.type),
229
+ span: binding.span ?? null,
230
+ }));
231
+ }
232
+
233
+ function semanticSpanForDiagnosticPath(
234
+ semantic: PointSemanticProgram,
235
+ program: PointCoreProgram,
236
+ path: string,
237
+ ): PointSourceSpan | null {
238
+ const fnMatch = path.match(/^fn\.([^.]+)(?:\.(.+))?$/);
239
+ if (fnMatch) {
240
+ const fnName = fnMatch[1] ?? "";
241
+ const suffix = fnMatch[2] ?? "";
242
+ const coreFn = program.declarations.find((declaration) => declaration.kind === "function" && declaration.name === fnName);
243
+ if (!coreFn || coreFn.kind !== "function" || !coreFn.semantic) return coreFn?.span ?? null;
244
+ const semanticDecl = semantic.declarations.find(
245
+ (declaration) => declaration.kind === coreFn.semantic!.kind && "name" in declaration && declaration.name === coreFn.semantic!.name,
246
+ );
247
+ if (!semanticDecl) return coreFn.span ?? null;
248
+ if (suffix.startsWith("if.condition") || suffix.endsWith(".condition")) {
249
+ return findConditionSpan(semanticDecl) ?? coreFn.span ?? null;
250
+ }
251
+ if (suffix === "return" || suffix.endsWith(".return")) {
252
+ return findReturnSpan(semanticDecl) ?? coreFn.span ?? null;
253
+ }
254
+ return ("span" in semanticDecl ? semanticDecl.span : null) ?? coreFn.span ?? null;
255
+ }
256
+ const typeMatch = path.match(/^type\.([^.]+)(?:\.(.+))?$/);
257
+ if (typeMatch) {
258
+ const typeName = typeMatch[1] ?? "";
259
+ const fieldName = typeMatch[2];
260
+ const coreType = program.declarations.find((declaration) => declaration.kind === "type" && declaration.name === typeName);
261
+ if (fieldName && coreType?.kind === "type") {
262
+ const field = coreType.fields.find((candidate) => candidate.name === fieldName);
263
+ return field?.span ?? coreType.span ?? null;
264
+ }
265
+ return coreType?.kind === "type" ? coreType.span ?? null : null;
266
+ }
267
+ return null;
268
+ }
269
+
270
+ function findConditionSpan(declaration: PointSemanticDeclaration): PointSourceSpan | null {
271
+ if (declaration.kind === "label") {
272
+ for (const statement of declaration.body) {
273
+ if (statement.kind === "whenReturn") return expressionSpan(statement.condition) ?? statement.span ?? null;
274
+ }
275
+ }
276
+ if (declaration.kind === "view") {
277
+ for (const statement of declaration.body) {
278
+ if (statement.kind === "whenRender") return expressionSpan(statement.condition) ?? statement.span ?? null;
279
+ }
280
+ }
281
+ if (declaration.kind === "rule") {
282
+ for (const statement of declaration.body) {
283
+ if (statement.kind === "addWhen") return expressionSpan(statement.condition) ?? statement.span ?? null;
284
+ }
285
+ }
286
+ if (declaration.kind === "policy") {
287
+ for (const statement of declaration.body) {
288
+ if (statement.kind === "allow" || statement.kind === "deny" || statement.kind === "require") {
289
+ return expressionSpan(statement.condition) ?? statement.span ?? null;
290
+ }
291
+ }
292
+ }
293
+ return null;
294
+ }
295
+
296
+ function findReturnSpan(declaration: PointSemanticDeclaration): PointSourceSpan | null {
297
+ if ("body" in declaration && Array.isArray(declaration.body)) {
298
+ for (const statement of declaration.body) {
299
+ if (statement.kind === "return") return expressionSpan(statement.value) ?? statement.span ?? null;
300
+ if (statement.kind === "otherwiseReturn") return expressionSpan(statement.value) ?? statement.span ?? null;
301
+ if (statement.kind === "render") return expressionSpan(statement.value) ?? statement.span ?? null;
302
+ }
303
+ }
304
+ return null;
305
+ }
306
+
307
+ function expressionSpan(expression: PointSemanticExpression): PointSourceSpan | null {
308
+ return expression.span ?? null;
309
+ }
310
+
311
+ function relatedRefsFor(symbol: PointSemanticSymbol, index: PointSemanticIndex): string[] {
312
+ if (symbol.kind === "field") {
313
+ const ownerPath = symbol.path.split(".").slice(0, 2).join(".");
314
+ return index.refs.filter((candidate) => candidate.path.startsWith(`${ownerPath}.`) && candidate.ref !== symbol.ref).map((candidate) => candidate.ref);
315
+ }
316
+ if (symbol.kind === "record" || symbol.kind === "calculation" || symbol.kind === "rule" || symbol.kind === "label" || symbol.kind === "action" || symbol.kind === "policy" || symbol.kind === "view" || symbol.kind === "route" || symbol.kind === "workflow" || symbol.kind === "command" || symbol.kind === "external") {
317
+ return index.refs.filter((candidate) => candidate.path.startsWith(`${symbol.path}.`)).map((candidate) => candidate.ref);
318
+ }
319
+ return [];
320
+ }
321
+
322
+ function summaryFor(symbol: PointSemanticSymbol): string {
323
+ if (symbol.kind === "module") return `Module ${symbol.name}.`;
324
+ if (symbol.kind === "use") return `Use ${symbol.name}${symbol.from ? ` from ${symbol.from}` : ""}.`;
325
+ if (symbol.kind === "record") return `Semantic record ${symbol.name}.`;
326
+ if (symbol.kind === "field") return `Field ${symbol.name}: ${symbol.type}.`;
327
+ if (symbol.kind === "param") return `Parameter ${symbol.name}: ${symbol.type}.`;
328
+ if (symbol.kind === "external") return `External function ${symbol.name} from ${symbol.from} returns ${symbol.type}.`;
329
+ if (symbol.kind === "calculation") return `Semantic calculation ${symbol.name} returns ${symbol.type}.`;
330
+ if (symbol.kind === "rule") return `Semantic rule ${symbol.name} returns ${symbol.type}.`;
331
+ if (symbol.kind === "label") return `Semantic label ${symbol.name} returns ${symbol.type}.`;
332
+ if (symbol.kind === "action") return `Semantic action ${symbol.name} returns ${symbol.type}; effects: ${(symbol.effects ?? []).join(", ") || "none"}.`;
333
+ if (symbol.kind === "policy") return `Semantic policy ${symbol.name} returns ${symbol.type}.`;
334
+ if (symbol.kind === "view") return `Semantic view ${symbol.name} returns ${symbol.type}.`;
335
+ if (symbol.kind === "route") return `Semantic route ${symbol.name} returns ${symbol.type}.`;
336
+ if (symbol.kind === "workflow") return `Semantic workflow ${symbol.name} returns ${symbol.type}.`;
337
+ if (symbol.kind === "command") return `Semantic command ${symbol.name} returns ${symbol.type}.`;
338
+ return `Point symbol ${symbol.name}.`;
339
+ }
340
+
341
+ function semanticRefFor(moduleName: string, path: string): string {
342
+ return `point://semantic/${moduleName}/${path}`;
343
+ }
344
+
345
+ function boolType(): PointSemanticTypeExpression {
346
+ return { kind: "typeRef", name: "Bool", args: [] };
347
+ }