@neocompose/cli 0.5.3 → 0.5.4

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/neo.mjs +566 -160
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.4] - 2026-07-19
4
+
5
+ ### Fixed
6
+
7
+ - Stop reporting phantom member updates when a stored type info spells an
8
+ empty generic-binding map as `typeArguments: {}` or `null` instead of an
9
+ absent key. Convex round trips produce these spellings interchangeably;
10
+ nested type-info binding maps now normalize to absent at every depth
11
+ before semantic comparison.
12
+
3
13
  ## [0.5.3] - 2026-07-19
4
14
 
5
15
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -1185,9 +1185,29 @@ function normalizeDocumentFields(recordKind, value) {
1185
1185
  }
1186
1186
  if (current === void 0) normalized[field] = null;
1187
1187
  }
1188
+ if (recordKind === "member") {
1189
+ normalizeTypeInfoBindings(normalized.returnTypeInfo);
1190
+ if (Array.isArray(normalized.argumentTypes)) {
1191
+ for (const argument2 of normalized.argumentTypes) {
1192
+ normalizeTypeInfoBindings(argument2);
1193
+ }
1194
+ }
1195
+ }
1188
1196
  omitRedundantDeclarationOrder(recordKind, normalized);
1189
1197
  return normalized;
1190
1198
  }
1199
+ function normalizeTypeInfoBindings(value) {
1200
+ if (!isRecord(value)) return;
1201
+ const bindings = value.typeArguments;
1202
+ if (bindings === null || isRecord(bindings) && Object.keys(bindings).length === 0) {
1203
+ delete value.typeArguments;
1204
+ } else if (isRecord(bindings)) {
1205
+ for (const binding of Object.values(bindings)) {
1206
+ normalizeTypeInfoBindings(binding);
1207
+ }
1208
+ }
1209
+ normalizeTypeInfoBindings(value.entryTypeInfo);
1210
+ }
1191
1211
  function memberDefaultApplies(field, type) {
1192
1212
  if (field === "isVirtual" || field === "isAbstract" || field === "locked" || field === "required") {
1193
1213
  return true;
@@ -12341,6 +12361,151 @@ var init_project_source_ast = __esm({
12341
12361
  }
12342
12362
  });
12343
12363
 
12364
+ // ../packages/neoscript-language/src/project-source-tokens.ts
12365
+ function rangeContains(range2, position) {
12366
+ return positionCompare(range2.start, position) <= 0 && positionCompare(position, range2.end) <= 0;
12367
+ }
12368
+ function rangeSize(range2) {
12369
+ return (range2.end.line - range2.start.line) * 1e6 + range2.end.character - range2.start.character;
12370
+ }
12371
+ function documentPrefix(text, position) {
12372
+ const lines = text.split("\n");
12373
+ return [
12374
+ ...lines.slice(0, position.line),
12375
+ (lines[position.line] ?? "").slice(0, position.character)
12376
+ ].join("\n");
12377
+ }
12378
+ function projectTokenAt(document, position) {
12379
+ const offset = new SourceText(document.text).offsetAt(position);
12380
+ const tokens = projectTokens(document);
12381
+ const containing = tokens.find(
12382
+ (token) => token.start <= offset && offset < token.end
12383
+ );
12384
+ if (containing?.kind === "identifier") return containing;
12385
+ const touching = tokens.find(
12386
+ (token) => token.kind === "identifier" && token.end === offset
12387
+ );
12388
+ return touching ?? containing;
12389
+ }
12390
+ function projectTokens(document) {
12391
+ const cached = PROJECT_TOKEN_CACHE.get(document);
12392
+ if (cached) return cached;
12393
+ const source = new SourceText(document.text);
12394
+ const tokens = lex(document.text).tokens.flatMap(
12395
+ (token) => {
12396
+ if (token.kind !== "string" || !token.raw.startsWith('"""')) {
12397
+ return [token];
12398
+ }
12399
+ const result = [];
12400
+ const contentEnd = token.raw.endsWith('"""') ? token.end - 3 : token.end;
12401
+ let segmentStart = token.start;
12402
+ let cursor = token.start + 3;
12403
+ while (cursor < contentEnd) {
12404
+ if (document.text[cursor] !== "{" || document.text[cursor + 1] === "{") {
12405
+ cursor++;
12406
+ continue;
12407
+ }
12408
+ const close = tripleInterpolationEnd(
12409
+ document.text,
12410
+ cursor + 1,
12411
+ contentEnd
12412
+ );
12413
+ if (close === null) break;
12414
+ if (segmentStart < cursor + 1) {
12415
+ result.push(
12416
+ projectToken(
12417
+ "string",
12418
+ document.text,
12419
+ source,
12420
+ segmentStart,
12421
+ cursor + 1
12422
+ )
12423
+ );
12424
+ }
12425
+ const expressionStart = cursor + 1;
12426
+ const expression = document.text.slice(expressionStart, close);
12427
+ for (const nested of lex(expression).tokens) {
12428
+ if (nested.kind === "eof") continue;
12429
+ const start = expressionStart + nested.start;
12430
+ const end = expressionStart + nested.end;
12431
+ result.push({
12432
+ ...nested,
12433
+ start,
12434
+ end,
12435
+ raw: document.text.slice(start, end),
12436
+ range: source.range(start, end)
12437
+ });
12438
+ }
12439
+ segmentStart = close;
12440
+ cursor = close + 1;
12441
+ }
12442
+ if (segmentStart < token.end) {
12443
+ result.push(
12444
+ projectToken(
12445
+ "string",
12446
+ document.text,
12447
+ source,
12448
+ segmentStart,
12449
+ token.end
12450
+ )
12451
+ );
12452
+ }
12453
+ return result;
12454
+ }
12455
+ );
12456
+ PROJECT_TOKEN_CACHE.set(document, tokens);
12457
+ return tokens;
12458
+ }
12459
+ function projectToken(kind, text, source, start, end) {
12460
+ const raw = text.slice(start, end);
12461
+ return { kind, text: raw, raw, start, end, range: source.range(start, end) };
12462
+ }
12463
+ function tripleInterpolationEnd(text, start, limit) {
12464
+ let depth = 0;
12465
+ let cursor = start;
12466
+ while (cursor < limit) {
12467
+ const char = text[cursor];
12468
+ const next = text[cursor + 1];
12469
+ if (char === '"') {
12470
+ cursor++;
12471
+ while (cursor < limit) {
12472
+ if (text[cursor] === "\\") cursor += 2;
12473
+ else if (text[cursor] === '"') {
12474
+ cursor++;
12475
+ break;
12476
+ } else cursor++;
12477
+ }
12478
+ continue;
12479
+ }
12480
+ if (char === "/" && next === "/") {
12481
+ while (cursor < limit && text[cursor] !== "\n") cursor++;
12482
+ continue;
12483
+ }
12484
+ if (char === "{") depth++;
12485
+ else if (char === "}") {
12486
+ if (depth === 0) return cursor;
12487
+ depth--;
12488
+ }
12489
+ cursor++;
12490
+ }
12491
+ return null;
12492
+ }
12493
+ function rangesEqual(left, right) {
12494
+ return positionCompare(left.start, right.start) === 0 && positionCompare(left.end, right.end) === 0;
12495
+ }
12496
+ function positionCompare(left, right) {
12497
+ return left.line === right.line ? left.character - right.character : left.line - right.line;
12498
+ }
12499
+ var PROJECT_TOKEN_CACHE;
12500
+ var init_project_source_tokens = __esm({
12501
+ "../packages/neoscript-language/src/project-source-tokens.ts"() {
12502
+ "use strict";
12503
+ init_lexer();
12504
+ init_source_text();
12505
+ PROJECT_TOKEN_CACHE = /* @__PURE__ */ new WeakMap();
12506
+ }
12507
+ });
12508
+
12344
12509
  // ../packages/neoscript-language/src/project-source-parser.ts
12345
12510
  function parseNeoProjectSource(sourceValue, kind) {
12346
12511
  const lexed = lex(sourceValue);
@@ -18792,7 +18957,13 @@ function analyzeNeoProjectSources(inputs) {
18792
18957
  const symbols = [];
18793
18958
  for (const [uri, document] of documents) {
18794
18959
  for (const declaration of document.declarations) {
18795
- collectDeclarationSymbols(uri, declaration, symbols, diagnostics);
18960
+ collectDeclarationSymbols(
18961
+ uri,
18962
+ document,
18963
+ declaration,
18964
+ symbols,
18965
+ diagnostics
18966
+ );
18796
18967
  }
18797
18968
  }
18798
18969
  validateSymbolUniqueness(symbols, diagnostics);
@@ -18802,7 +18973,7 @@ function analyzeNeoProjectSources(inputs) {
18802
18973
  diagnostics.push(...compileProjectSourceBodies(documents).diagnostics);
18803
18974
  return { documents, symbols, diagnostics };
18804
18975
  }
18805
- function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18976
+ function collectDeclarationSymbols(uri, document, declaration, symbols, diagnostics) {
18806
18977
  const declarationKind = declaration.kind;
18807
18978
  collectSymbol(
18808
18979
  uri,
@@ -18812,7 +18983,9 @@ function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18812
18983
  declaration.annotations,
18813
18984
  void 0,
18814
18985
  symbols,
18815
- diagnostics
18986
+ diagnostics,
18987
+ void 0,
18988
+ declaration.kind === "global" ? declaration.type.name : void 0
18816
18989
  );
18817
18990
  if (declaration.kind === "enum") {
18818
18991
  for (const option of declaration.options) {
@@ -18829,7 +19002,10 @@ function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18829
19002
  }
18830
19003
  return;
18831
19004
  }
18832
- if (declaration.kind === "global") return;
19005
+ if (declaration.kind === "global") {
19006
+ collectGlobalChildSymbols(uri, document, declaration, symbols, diagnostics);
19007
+ return;
19008
+ }
18833
19009
  if (declaration.kind === "class") {
18834
19010
  for (const parameter3 of declaration.genericParameters) {
18835
19011
  collectSymbol(
@@ -18886,6 +19062,104 @@ function collectDeclarationSymbols(uri, declaration, symbols, diagnostics) {
18886
19062
  }
18887
19063
  }
18888
19064
  }
19065
+ function collectGlobalChildSymbols(uri, document, declaration, symbols, diagnostics) {
19066
+ if (declaration.type.name === "Root") return;
19067
+ const initializer = declaration.initializer;
19068
+ const tokens = lex(document.sourceText).tokens.filter(
19069
+ (token) => token.kind !== "comment" && token.kind !== "eof" && rangeWithin(initializer.range, token.range)
19070
+ );
19071
+ let braceDepth = 0;
19072
+ let groupDepth = 0;
19073
+ let pendingAnnotations = [];
19074
+ for (let index = 0; index < tokens.length; index++) {
19075
+ const token = tokens[index];
19076
+ if (token.text === "{") {
19077
+ braceDepth++;
19078
+ continue;
19079
+ }
19080
+ if (token.text === "}") {
19081
+ braceDepth--;
19082
+ continue;
19083
+ }
19084
+ if (braceDepth !== 1) continue;
19085
+ if (token.text === "(" || token.text === "[") {
19086
+ groupDepth++;
19087
+ continue;
19088
+ }
19089
+ if (token.text === ")" || token.text === "]") {
19090
+ groupDepth--;
19091
+ continue;
19092
+ }
19093
+ if (groupDepth !== 0) continue;
19094
+ if (token.text === ";") {
19095
+ pendingAnnotations = [];
19096
+ continue;
19097
+ }
19098
+ if (token.text === "@") {
19099
+ const annotation2 = scanAnnotation(tokens, index);
19100
+ if (annotation2) {
19101
+ pendingAnnotations.push(annotation2.annotation);
19102
+ index = annotation2.endIndex;
19103
+ }
19104
+ continue;
19105
+ }
19106
+ const name = tokens[index + 1];
19107
+ if ((token.kind === "type" || token.kind === "identifier") && name?.kind === "identifier" && tokens[index + 2]?.text === "=" && tokens[index + 3]?.text !== "=" && // A pending annotation or statement boundary precedes a declaration;
19108
+ // `key: value` fields and expression member accesses never do.
19109
+ tokens[index - 1]?.text !== "." && tokens[index - 1]?.text !== ":") {
19110
+ collectSymbol(
19111
+ uri,
19112
+ "graphChild",
19113
+ name.text,
19114
+ name.range,
19115
+ pendingAnnotations,
19116
+ declaration.name,
19117
+ symbols,
19118
+ diagnostics,
19119
+ void 0,
19120
+ token.text
19121
+ );
19122
+ pendingAnnotations = [];
19123
+ index += 2;
19124
+ }
19125
+ }
19126
+ }
19127
+ function scanAnnotation(tokens, atIndex) {
19128
+ const marker = tokens[atIndex];
19129
+ const name = tokens[atIndex + 1];
19130
+ if (!name || name.kind !== "identifier" && name.kind !== "keyword") {
19131
+ return null;
19132
+ }
19133
+ const argumentsList2 = [];
19134
+ let endIndex = atIndex + 1;
19135
+ let endRange = name.range;
19136
+ if (tokens[atIndex + 2]?.text === "(") {
19137
+ let depth = 0;
19138
+ for (let index = atIndex + 2; index < tokens.length; index++) {
19139
+ const token = tokens[index];
19140
+ if (token.text === "(") depth++;
19141
+ else if (token.text === ")" && --depth === 0) {
19142
+ endIndex = index;
19143
+ endRange = token.range;
19144
+ break;
19145
+ } else if (depth === 1 && token.kind === "string") {
19146
+ argumentsList2.push({ text: token.raw, range: token.range });
19147
+ }
19148
+ }
19149
+ }
19150
+ return {
19151
+ annotation: {
19152
+ name: name.text,
19153
+ nameRange: name.range,
19154
+ arguments: argumentsList2,
19155
+ range: { start: marker.range.start, end: endRange.end }
19156
+ },
19157
+ endIndex
19158
+ };
19159
+ }
19160
+ function rangeWithin(outer, inner) {
19161
+ return positionCompare(outer.start, inner.start) <= 0 && positionCompare(inner.end, outer.end) <= 0;
19162
+ }
18889
19163
  function collectFlowSymbols(uri, content, ownerName, scopeRange, symbols, diagnostics) {
18890
19164
  for (const binding of content.bindings) {
18891
19165
  collectSymbol(
@@ -19177,6 +19451,8 @@ var init_project_source_analysis = __esm({
19177
19451
  "../packages/neoscript-language/src/project-source-analysis.ts"() {
19178
19452
  "use strict";
19179
19453
  init_language_spec();
19454
+ init_lexer();
19455
+ init_project_source_tokens();
19180
19456
  init_project_source_parser();
19181
19457
  init_project_source_semantics();
19182
19458
  init_project_source_script_compiler();
@@ -21242,151 +21518,6 @@ var init_project_schema_manifest = __esm({
21242
21518
  }
21243
21519
  });
21244
21520
 
21245
- // ../packages/neoscript-language/src/project-source-tokens.ts
21246
- function rangeContains(range2, position) {
21247
- return positionCompare(range2.start, position) <= 0 && positionCompare(position, range2.end) <= 0;
21248
- }
21249
- function rangeSize(range2) {
21250
- return (range2.end.line - range2.start.line) * 1e6 + range2.end.character - range2.start.character;
21251
- }
21252
- function documentPrefix(text, position) {
21253
- const lines = text.split("\n");
21254
- return [
21255
- ...lines.slice(0, position.line),
21256
- (lines[position.line] ?? "").slice(0, position.character)
21257
- ].join("\n");
21258
- }
21259
- function projectTokenAt(document, position) {
21260
- const offset = new SourceText(document.text).offsetAt(position);
21261
- const tokens = projectTokens(document);
21262
- const containing = tokens.find(
21263
- (token) => token.start <= offset && offset < token.end
21264
- );
21265
- if (containing?.kind === "identifier") return containing;
21266
- const touching = tokens.find(
21267
- (token) => token.kind === "identifier" && token.end === offset
21268
- );
21269
- return touching ?? containing;
21270
- }
21271
- function projectTokens(document) {
21272
- const cached = PROJECT_TOKEN_CACHE.get(document);
21273
- if (cached) return cached;
21274
- const source = new SourceText(document.text);
21275
- const tokens = lex(document.text).tokens.flatMap(
21276
- (token) => {
21277
- if (token.kind !== "string" || !token.raw.startsWith('"""')) {
21278
- return [token];
21279
- }
21280
- const result = [];
21281
- const contentEnd = token.raw.endsWith('"""') ? token.end - 3 : token.end;
21282
- let segmentStart = token.start;
21283
- let cursor = token.start + 3;
21284
- while (cursor < contentEnd) {
21285
- if (document.text[cursor] !== "{" || document.text[cursor + 1] === "{") {
21286
- cursor++;
21287
- continue;
21288
- }
21289
- const close = tripleInterpolationEnd(
21290
- document.text,
21291
- cursor + 1,
21292
- contentEnd
21293
- );
21294
- if (close === null) break;
21295
- if (segmentStart < cursor + 1) {
21296
- result.push(
21297
- projectToken(
21298
- "string",
21299
- document.text,
21300
- source,
21301
- segmentStart,
21302
- cursor + 1
21303
- )
21304
- );
21305
- }
21306
- const expressionStart = cursor + 1;
21307
- const expression = document.text.slice(expressionStart, close);
21308
- for (const nested of lex(expression).tokens) {
21309
- if (nested.kind === "eof") continue;
21310
- const start = expressionStart + nested.start;
21311
- const end = expressionStart + nested.end;
21312
- result.push({
21313
- ...nested,
21314
- start,
21315
- end,
21316
- raw: document.text.slice(start, end),
21317
- range: source.range(start, end)
21318
- });
21319
- }
21320
- segmentStart = close;
21321
- cursor = close + 1;
21322
- }
21323
- if (segmentStart < token.end) {
21324
- result.push(
21325
- projectToken(
21326
- "string",
21327
- document.text,
21328
- source,
21329
- segmentStart,
21330
- token.end
21331
- )
21332
- );
21333
- }
21334
- return result;
21335
- }
21336
- );
21337
- PROJECT_TOKEN_CACHE.set(document, tokens);
21338
- return tokens;
21339
- }
21340
- function projectToken(kind, text, source, start, end) {
21341
- const raw = text.slice(start, end);
21342
- return { kind, text: raw, raw, start, end, range: source.range(start, end) };
21343
- }
21344
- function tripleInterpolationEnd(text, start, limit) {
21345
- let depth = 0;
21346
- let cursor = start;
21347
- while (cursor < limit) {
21348
- const char = text[cursor];
21349
- const next = text[cursor + 1];
21350
- if (char === '"') {
21351
- cursor++;
21352
- while (cursor < limit) {
21353
- if (text[cursor] === "\\") cursor += 2;
21354
- else if (text[cursor] === '"') {
21355
- cursor++;
21356
- break;
21357
- } else cursor++;
21358
- }
21359
- continue;
21360
- }
21361
- if (char === "/" && next === "/") {
21362
- while (cursor < limit && text[cursor] !== "\n") cursor++;
21363
- continue;
21364
- }
21365
- if (char === "{") depth++;
21366
- else if (char === "}") {
21367
- if (depth === 0) return cursor;
21368
- depth--;
21369
- }
21370
- cursor++;
21371
- }
21372
- return null;
21373
- }
21374
- function rangesEqual(left, right) {
21375
- return positionCompare(left.start, right.start) === 0 && positionCompare(left.end, right.end) === 0;
21376
- }
21377
- function positionCompare(left, right) {
21378
- return left.line === right.line ? left.character - right.character : left.line - right.line;
21379
- }
21380
- var PROJECT_TOKEN_CACHE;
21381
- var init_project_source_tokens = __esm({
21382
- "../packages/neoscript-language/src/project-source-tokens.ts"() {
21383
- "use strict";
21384
- init_lexer();
21385
- init_source_text();
21386
- PROJECT_TOKEN_CACHE = /* @__PURE__ */ new WeakMap();
21387
- }
21388
- });
21389
-
21390
21521
  // ../packages/neoscript-language/src/quick-fixes.ts
21391
21522
  function quickFixFor(document, diagnostic) {
21392
21523
  if (diagnostic.code === "missing-semicolon") {
@@ -21703,11 +21834,17 @@ function projectMemberCompletions(analysis, document, position) {
21703
21834
  if (!owner || owner.kind !== "identifier") return null;
21704
21835
  const resolvedOwner = projectSymbolAt(analysis, document, owner.range.start);
21705
21836
  if (!resolvedOwner) return null;
21837
+ const ownerNames = /* @__PURE__ */ new Set();
21706
21838
  const typeName = projectSymbolTypeName(resolvedOwner.symbol);
21707
- if (!typeName) return null;
21708
- return analysis.symbols.filter(
21709
- (symbol) => symbol.kind === "member" && symbol.ownerName === typeName
21839
+ if (typeName) ownerNames.add(typeName);
21840
+ if (resolvedOwner.symbol.kind === "global") {
21841
+ ownerNames.add(resolvedOwner.symbol.name);
21842
+ }
21843
+ if (ownerNames.size === 0) return null;
21844
+ const members = analysis.symbols.filter(
21845
+ (symbol) => (symbol.kind === "member" || symbol.kind === "graphChild") && symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
21710
21846
  );
21847
+ return members.length > 0 ? members : null;
21711
21848
  }
21712
21849
  function projectConstructorArgumentCompletions(analysis, document, position) {
21713
21850
  const tokens = lex(document.text).tokens.filter(
@@ -21799,6 +21936,15 @@ function graphConstructorNamedArguments(typeName) {
21799
21936
  { name: "reason", type: "string" },
21800
21937
  { name: "duration", type: "decimal?" }
21801
21938
  ];
21939
+ case "Vector2":
21940
+ case "Vector2Int":
21941
+ case "Vector3":
21942
+ case "Vector3Int":
21943
+ case "Color":
21944
+ return graphConstructorParameters(typeName).map((parameter3) => ({
21945
+ name: parameter3.name,
21946
+ type: parameter3.type
21947
+ }));
21802
21948
  default:
21803
21949
  return null;
21804
21950
  }
@@ -22234,6 +22380,19 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
22234
22380
  return { token, symbol: visibleCandidates[0] };
22235
22381
  }
22236
22382
  if (visibleCandidates.length === 0) return null;
22383
+ const sourceTokens = projectTokens(document);
22384
+ const tokenIndex = sourceTokens.findIndex(
22385
+ (candidate) => candidate.start === token.start && candidate.end === token.end
22386
+ );
22387
+ if (sourceTokens[tokenIndex + 1]?.text === "=" && sourceTokens[tokenIndex + 2]?.text !== "=") {
22388
+ const constructed = constructionMemberSymbol(
22389
+ analysis,
22390
+ document,
22391
+ token,
22392
+ visibleCandidates
22393
+ );
22394
+ if (constructed) return { token, symbol: constructed };
22395
+ }
22237
22396
  const source = analysis.documents.get(document.uri);
22238
22397
  const owner = source ? declarationContaining(source, token.range.start)?.name : void 0;
22239
22398
  if (owner) {
@@ -22242,10 +22401,6 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
22242
22401
  );
22243
22402
  if (owned.length === 1) return { token, symbol: owned[0] };
22244
22403
  }
22245
- const sourceTokens = projectTokens(document);
22246
- const tokenIndex = sourceTokens.findIndex(
22247
- (candidate) => candidate.start === token.start && candidate.end === token.end
22248
- );
22249
22404
  const dotOwner = sourceTokens[tokenIndex - 1]?.text === "." ? sourceTokens[tokenIndex - 2] : void 0;
22250
22405
  if (dotOwner) {
22251
22406
  const resolvedOwner = indexedProjectSymbolAt(
@@ -22254,15 +22409,39 @@ function indexedProjectSymbolAt(analysis, document, position, index) {
22254
22409
  dotOwner.range.start,
22255
22410
  index
22256
22411
  );
22257
- const ownerType = resolvedOwner ? projectSymbolTypeName(resolvedOwner.symbol) : dotOwner.text;
22412
+ const ownerNames = /* @__PURE__ */ new Set();
22413
+ if (resolvedOwner) {
22414
+ const typeName = projectSymbolTypeName(resolvedOwner.symbol);
22415
+ if (typeName) ownerNames.add(typeName);
22416
+ if (resolvedOwner.symbol.kind === "global") {
22417
+ ownerNames.add(resolvedOwner.symbol.name);
22418
+ }
22419
+ } else {
22420
+ ownerNames.add(dotOwner.text);
22421
+ }
22258
22422
  const qualified = visibleCandidates.filter(
22259
- (symbol) => symbol.ownerName === ownerType
22423
+ (symbol) => symbol.ownerName !== void 0 && ownerNames.has(symbol.ownerName)
22260
22424
  );
22261
22425
  if (qualified.length === 1) return { token, symbol: qualified[0] };
22262
22426
  }
22263
22427
  const projectLevel = visibleCandidates.filter((symbol) => !symbol.ownerName);
22264
22428
  return projectLevel.length === 1 ? { token, symbol: projectLevel[0] } : null;
22265
22429
  }
22430
+ function constructionMemberSymbol(analysis, document, token, candidates) {
22431
+ const site = constructionSiteAt(analysis, document, token.range.start);
22432
+ let current = site?.enclosingTypeName ?? null;
22433
+ const visited = /* @__PURE__ */ new Set();
22434
+ while (current !== null && !visited.has(current)) {
22435
+ visited.add(current);
22436
+ const owned = candidates.find(
22437
+ (symbol) => symbol.kind === "member" && symbol.ownerName === current
22438
+ );
22439
+ if (owned) return owned;
22440
+ const declaration = findTypeDeclaration(analysis, current);
22441
+ current = declaration?.kind === "class" ? resolvableBaseClassName(analysis, declaration) : null;
22442
+ }
22443
+ return null;
22444
+ }
22266
22445
  function projectSymbolTypeName(symbol) {
22267
22446
  switch (symbol.kind) {
22268
22447
  case "class":
@@ -22360,21 +22539,219 @@ function signatureResult(name, parameters, activeParameter) {
22360
22539
  };
22361
22540
  }
22362
22541
  function contextualConstructorType(analysis, document, position) {
22542
+ return constructionSiteAt(analysis, document, position)?.expectedTypeName ?? null;
22543
+ }
22544
+ function initializerRootAt(analysis, document, position) {
22363
22545
  const source = analysis.documents.get(document.uri);
22364
22546
  if (!source) return null;
22365
22547
  for (const declaration of source.declarations) {
22366
22548
  if (declaration.kind === "global") {
22367
22549
  if (rangeContains(declaration.initializer.range, position)) {
22368
- return declaration.type.name;
22550
+ return {
22551
+ type: declaration.type,
22552
+ range: declaration.initializer.range
22553
+ };
22369
22554
  }
22370
22555
  continue;
22371
22556
  }
22372
22557
  if (declaration.kind === "enum") continue;
22373
22558
  for (const member of declaration.members) {
22374
22559
  if (member.kind === "field" && member.initializer && rangeContains(member.initializer.range, position)) {
22375
- return member.type.name;
22560
+ return { type: member.type, range: member.initializer.range };
22561
+ }
22562
+ }
22563
+ }
22564
+ return null;
22565
+ }
22566
+ function constructionSiteAt(analysis, document, position) {
22567
+ const root = initializerRootAt(analysis, document, position);
22568
+ if (!root) return null;
22569
+ const tokens = projectTokens(document).filter(
22570
+ (token) => token.kind !== "comment" && token.kind !== "eof" && rangeContains(root.range, token.range.start) && positionCompare(token.range.start, position) < 0
22571
+ );
22572
+ const stack = [];
22573
+ const expectedType = () => {
22574
+ const frame = stack.at(-1);
22575
+ if (!frame) return root.type;
22576
+ switch (frame.kind) {
22577
+ case "object":
22578
+ case "dictionary":
22579
+ return frame.slotType;
22580
+ case "list":
22581
+ return frame.elementType;
22582
+ case "call":
22583
+ return constructorArgumentType(analysis, frame);
22584
+ case "opaque":
22585
+ return null;
22586
+ }
22587
+ };
22588
+ let sawNew = false;
22589
+ let pendingNewTypeName = null;
22590
+ let closedConstructorTypeName = null;
22591
+ for (let index = 0; index < tokens.length; index++) {
22592
+ const token = tokens[index];
22593
+ if (token.text === "@") {
22594
+ if ((tokens[index + 1]?.kind === "identifier" || tokens[index + 1]?.kind === "keyword") && tokens[index + 2]?.text === "(") {
22595
+ let depth = 0;
22596
+ index += 2;
22597
+ for (; index < tokens.length; index++) {
22598
+ if (tokens[index].text === "(") depth++;
22599
+ else if (tokens[index].text === ")" && --depth === 0) break;
22600
+ }
22601
+ } else if (tokens[index + 1]?.kind === "identifier") {
22602
+ index += 1;
22603
+ }
22604
+ continue;
22605
+ }
22606
+ if (token.text === "new") {
22607
+ sawNew = true;
22608
+ pendingNewTypeName = expectedType()?.name ?? null;
22609
+ closedConstructorTypeName = null;
22610
+ continue;
22611
+ }
22612
+ if (sawNew && (token.kind === "identifier" || token.kind === "type")) {
22613
+ pendingNewTypeName = token.text;
22614
+ continue;
22615
+ }
22616
+ if (sawNew && token.text === "<") {
22617
+ let depth = 0;
22618
+ for (; index < tokens.length; index++) {
22619
+ if (tokens[index].text === "<") depth++;
22620
+ else if (tokens[index].text === ">" && --depth === 0) break;
22621
+ }
22622
+ continue;
22623
+ }
22624
+ if (token.text === "{") {
22625
+ if (sawNew || closedConstructorTypeName !== null) {
22626
+ stack.push({
22627
+ kind: "object",
22628
+ typeName: pendingNewTypeName ?? closedConstructorTypeName,
22629
+ slotType: null
22630
+ });
22631
+ sawNew = false;
22632
+ pendingNewTypeName = null;
22633
+ } else {
22634
+ const expected = expectedType();
22635
+ if (expected?.name === "Dictionary") {
22636
+ stack.push({
22637
+ kind: "dictionary",
22638
+ valueType: expected.typeArguments[1] ?? null,
22639
+ slotType: null
22640
+ });
22641
+ } else {
22642
+ stack.push({ kind: "opaque" });
22643
+ }
22376
22644
  }
22645
+ closedConstructorTypeName = null;
22646
+ continue;
22647
+ }
22648
+ if (token.text === "(") {
22649
+ if (sawNew) {
22650
+ stack.push({
22651
+ kind: "call",
22652
+ typeName: pendingNewTypeName,
22653
+ argumentIndex: 0,
22654
+ argumentName: null
22655
+ });
22656
+ sawNew = false;
22657
+ pendingNewTypeName = null;
22658
+ } else {
22659
+ stack.push({ kind: "opaque" });
22660
+ }
22661
+ closedConstructorTypeName = null;
22662
+ continue;
22663
+ }
22664
+ if (token.text === "[") {
22665
+ stack.push({
22666
+ kind: "list",
22667
+ elementType: collectionElementType2(expectedType())
22668
+ });
22669
+ closedConstructorTypeName = null;
22670
+ continue;
22671
+ }
22672
+ if (token.text === ")") {
22673
+ const frame2 = stack.pop();
22674
+ closedConstructorTypeName = frame2?.kind === "call" ? frame2.typeName : null;
22675
+ continue;
22676
+ }
22677
+ if (token.text === "}" || token.text === "]") {
22678
+ stack.pop();
22679
+ closedConstructorTypeName = null;
22680
+ continue;
22681
+ }
22682
+ closedConstructorTypeName = null;
22683
+ const frame = stack.at(-1);
22684
+ if (!frame) continue;
22685
+ if (frame.kind === "object") {
22686
+ if (token.text === "=" && tokens[index + 1]?.text !== "=") {
22687
+ const nameToken = tokens[index - 1];
22688
+ frame.slotType = nameToken && (nameToken.kind === "identifier" || nameToken.kind === "type") ? declaredFieldType(analysis, frame.typeName, nameToken.text) : null;
22689
+ } else if (token.text === ",") {
22690
+ frame.slotType = null;
22691
+ }
22692
+ } else if (frame.kind === "dictionary") {
22693
+ if (token.text === ":") frame.slotType = frame.valueType;
22694
+ else if (token.text === ",") frame.slotType = null;
22695
+ } else if (frame.kind === "call") {
22696
+ if (token.text === ",") {
22697
+ frame.argumentIndex++;
22698
+ frame.argumentName = null;
22699
+ } else if (token.text === ":") {
22700
+ const nameToken = tokens[index - 1];
22701
+ frame.argumentName = nameToken?.kind === "identifier" ? nameToken.text : null;
22702
+ }
22703
+ }
22704
+ }
22705
+ const enclosing = [...stack].reverse().find(
22706
+ (frame) => frame.kind === "object"
22707
+ );
22708
+ return {
22709
+ expectedTypeName: expectedType()?.name ?? null,
22710
+ enclosingTypeName: enclosing?.typeName ?? null
22711
+ };
22712
+ }
22713
+ function collectionElementType2(type) {
22714
+ if (!type) return null;
22715
+ if (type.name === "List" || type.name === "Set") {
22716
+ return type.typeArguments[0] ?? null;
22717
+ }
22718
+ return null;
22719
+ }
22720
+ function constructorArgumentType(analysis, frame) {
22721
+ if (!frame.typeName) return null;
22722
+ const declaration = findTypeDeclaration(analysis, frame.typeName);
22723
+ if (!declaration || declaration.kind === "global" || declaration.kind === "enum") {
22724
+ return null;
22725
+ }
22726
+ const fields = declaration.members.filter(
22727
+ (member) => member.kind === "field" && !member.modifiers.includes("static")
22728
+ );
22729
+ if (frame.argumentName !== null) {
22730
+ return fields.find((field) => field.name === frame.argumentName)?.type ?? null;
22731
+ }
22732
+ return fields[frame.argumentIndex]?.type ?? null;
22733
+ }
22734
+ function declaredFieldType(analysis, typeName, fieldName) {
22735
+ let current = typeName;
22736
+ const visited = /* @__PURE__ */ new Set();
22737
+ while (current !== null && !visited.has(current)) {
22738
+ visited.add(current);
22739
+ const declaration = findTypeDeclaration(analysis, current);
22740
+ if (!declaration || declaration.kind === "global" || declaration.kind === "enum") {
22741
+ return null;
22377
22742
  }
22743
+ const member = declaration.members.find(
22744
+ (candidate) => candidate.kind === "field" && candidate.name === fieldName
22745
+ );
22746
+ if (member) return member.type;
22747
+ current = declaration.kind === "class" ? resolvableBaseClassName(analysis, declaration) : null;
22748
+ }
22749
+ return null;
22750
+ }
22751
+ function resolvableBaseClassName(analysis, declaration) {
22752
+ for (const base of declaration.baseTypes) {
22753
+ const resolved = findTypeDeclaration(analysis, base.name);
22754
+ if (resolved?.kind === "class") return base.name;
22378
22755
  }
22379
22756
  return null;
22380
22757
  }
@@ -22448,6 +22825,35 @@ function graphConstructorParameters(typeName) {
22448
22825
  { type: "string", name: "reason" },
22449
22826
  { type: "decimal?", name: "duration" }
22450
22827
  ];
22828
+ case "Vector2":
22829
+ return [
22830
+ { type: "float", name: "x" },
22831
+ { type: "float", name: "y" }
22832
+ ];
22833
+ case "Vector2Int":
22834
+ return [
22835
+ { type: "int", name: "x" },
22836
+ { type: "int", name: "y" }
22837
+ ];
22838
+ case "Vector3":
22839
+ return [
22840
+ { type: "float", name: "x" },
22841
+ { type: "float", name: "y" },
22842
+ { type: "float", name: "z" }
22843
+ ];
22844
+ case "Vector3Int":
22845
+ return [
22846
+ { type: "int", name: "x" },
22847
+ { type: "int", name: "y" },
22848
+ { type: "int", name: "z" }
22849
+ ];
22850
+ case "Color":
22851
+ return [
22852
+ { type: "float", name: "r" },
22853
+ { type: "float", name: "g" },
22854
+ { type: "float", name: "b" },
22855
+ { type: "float", name: "a" }
22856
+ ];
22451
22857
  default:
22452
22858
  return [];
22453
22859
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",