@neocompose/cli 0.31.7 → 0.31.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.8] - 2026-08-17
4
+
5
+ ### Fixed
6
+
7
+ - Propagate `?.` through following required member and index accesses in the
8
+ same expression chain, so a nullable parent safely short-circuits a deep
9
+ read while independently nullable descendants still require explicit null
10
+ handling.
11
+
3
12
  ## [0.31.7] - 2026-08-15
4
13
 
5
14
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -11838,13 +11838,15 @@ var init_strict_resolver = __esm({
11838
11838
  };
11839
11839
  }
11840
11840
  }
11841
- const receiver = this.resolveExpression(receiverAst, scope);
11842
- if (optional && !isNullable(receiver.type)) {
11841
+ const resolvedReceiver = this.resolveExpression(receiverAst, scope);
11842
+ if (optional && !isNullable(resolvedReceiver.type)) {
11843
11843
  throw new CompileError(
11844
- `\`?.\` requires an optional receiver; got ${this.describe(receiver.type)}`,
11844
+ `\`?.\` requires an optional receiver; got ${this.describe(resolvedReceiver.type)}`,
11845
11845
  pos
11846
11846
  );
11847
11847
  }
11848
+ const receiver = resolvedReceiver.optionalChainType ? { ...resolvedReceiver, type: resolvedReceiver.optionalChainType } : resolvedReceiver;
11849
+ const nullPropagating = optional || resolvedReceiver.optionalChainType !== void 0;
11848
11850
  if (!optional && isNullable(receiver.type)) {
11849
11851
  throw new CompileError(
11850
11852
  `Member '${name}' is read on ${this.describe(receiver.type)}, which may be null. Handle null before reading through it \u2014 use \`!\` if it is always set, or \`?.\`, \`??\`, or a null check that narrows it.`,
@@ -11852,7 +11854,7 @@ var init_strict_resolver = __esm({
11852
11854
  );
11853
11855
  }
11854
11856
  if (receiver.staticType?.kind === "enum") {
11855
- if (optional) {
11857
+ if (nullPropagating) {
11856
11858
  throw new CompileError(
11857
11859
  `\`?.\` is not valid on enum type '${receiver.staticType.name}'; enum option access is always defined`,
11858
11860
  pos
@@ -11924,9 +11926,10 @@ var init_strict_resolver = __esm({
11924
11926
  pointer: receiver.pointer,
11925
11927
  key: numberPointer(0)
11926
11928
  },
11927
- ...optional ? { optional: true } : {}
11929
+ ...nullPropagating ? { optional: true } : {}
11928
11930
  },
11929
- type: optional ? { ...primitiveMember.type, nullable: true } : primitiveMember.type,
11931
+ type: nullPropagating ? { ...primitiveMember.type, nullable: true } : primitiveMember.type,
11932
+ ...nullPropagating ? { optionalChainType: primitiveMember.type } : {},
11930
11933
  ...receiver.writability ? { writability: receiver.writability } : {}
11931
11934
  };
11932
11935
  }
@@ -11934,7 +11937,7 @@ var init_strict_resolver = __esm({
11934
11937
  receiver,
11935
11938
  primitiveMember.key ?? name,
11936
11939
  primitiveMember.type,
11937
- optional
11940
+ nullPropagating
11938
11941
  );
11939
11942
  if (!primitiveMember.derived) return resolved;
11940
11943
  return {
@@ -11968,7 +11971,7 @@ var init_strict_resolver = __esm({
11968
11971
  receiver,
11969
11972
  name,
11970
11973
  { kind: "primitive", name: "string" },
11971
- optional
11974
+ nullPropagating
11972
11975
  );
11973
11976
  }
11974
11977
  const member = type.members.find((candidate) => candidate.name === name);
@@ -12002,9 +12005,10 @@ var init_strict_resolver = __esm({
12002
12005
  type: "callGetter" /* CallGetter */,
12003
12006
  memberId: member.id,
12004
12007
  receiver: { kind: "instance", pointer: receiver.pointer },
12005
- ...optional ? { optional: true } : {}
12008
+ ...nullPropagating ? { optional: true } : {}
12006
12009
  },
12007
- type: optional ? { ...memberType2, nullable: true } : memberType2,
12010
+ type: nullPropagating ? { ...memberType2, nullable: true } : memberType2,
12011
+ ...nullPropagating ? { optionalChainType: memberType2 } : {},
12008
12012
  symbol: member,
12009
12013
  ...member.writable ? {
12010
12014
  writability: member.writability ? toWritability(member.writability) : "setter" /* Setter */
@@ -12028,7 +12032,7 @@ var init_strict_resolver = __esm({
12028
12032
  receiver,
12029
12033
  member.schemaKey ?? member.name,
12030
12034
  memberType2,
12031
- optional,
12035
+ nullPropagating,
12032
12036
  member.id
12033
12037
  ),
12034
12038
  symbol: member,
@@ -12136,14 +12140,23 @@ var init_strict_resolver = __esm({
12136
12140
  ...optional ? { optional: true } : {}
12137
12141
  },
12138
12142
  type: optional ? { ...type, nullable: true } : type,
12143
+ ...optional ? { optionalChainType: type } : {},
12139
12144
  ...receiver.writability ? { writability: receiver.writability } : {}
12140
12145
  };
12141
12146
  }
12142
12147
  resolveIndex(receiverAst, indexAst, scope, pos, optional) {
12143
- const receiver = this.resolveExpression(receiverAst, scope);
12144
- if (optional && !isNullable(receiver.type)) {
12148
+ const resolvedReceiver = this.resolveExpression(receiverAst, scope);
12149
+ if (optional && !isNullable(resolvedReceiver.type)) {
12145
12150
  throw new CompileError(
12146
- `\`?.[]\` requires an optional receiver; got ${this.describe(receiver.type)}`,
12151
+ `\`?.[]\` requires an optional receiver; got ${this.describe(resolvedReceiver.type)}`,
12152
+ pos
12153
+ );
12154
+ }
12155
+ const receiver = resolvedReceiver.optionalChainType ? { ...resolvedReceiver, type: resolvedReceiver.optionalChainType } : resolvedReceiver;
12156
+ const nullPropagating = optional || resolvedReceiver.optionalChainType !== void 0;
12157
+ if (!optional && isNullable(receiver.type)) {
12158
+ throw new CompileError(
12159
+ `Index access is read on ${this.describe(receiver.type)}, which may be null. Handle null before indexing it \u2014 use \`!\` if it is always set, or \`?.[]\`, \`??\`, or a null check that narrows it.`,
12147
12160
  pos
12148
12161
  );
12149
12162
  }
@@ -12185,9 +12198,10 @@ var init_strict_resolver = __esm({
12185
12198
  pointer: {
12186
12199
  type: "keyOf" /* KeyOf */,
12187
12200
  keyOf: { pointer: receiver.pointer, key: requested.pointer },
12188
- ...optional ? { optional: true } : {}
12201
+ ...nullPropagating ? { optional: true } : {}
12189
12202
  },
12190
- type: optional ? { ...receiver.type.elementType, nullable: true } : receiver.type.elementType,
12203
+ type: nullPropagating ? { ...receiver.type.elementType, nullable: true } : receiver.type.elementType,
12204
+ ...nullPropagating ? { optionalChainType: receiver.type.elementType } : {},
12191
12205
  ...receiver.writability ? { writability: receiver.writability } : {}
12192
12206
  };
12193
12207
  }
@@ -12218,9 +12232,10 @@ var init_strict_resolver = __esm({
12218
12232
  pointer: {
12219
12233
  type: "keyOf" /* KeyOf */,
12220
12234
  keyOf: { pointer: receiver.pointer, key: index.pointer },
12221
- ...optional ? { optional: true } : {}
12235
+ ...nullPropagating ? { optional: true } : {}
12222
12236
  },
12223
- type: optional ? { ...valueType, nullable: true } : valueType,
12237
+ type: nullPropagating ? { ...valueType, nullable: true } : valueType,
12238
+ ...nullPropagating ? { optionalChainType: valueType } : {},
12224
12239
  ...receiver.writability ? { writability: receiver.writability } : {}
12225
12240
  };
12226
12241
  }
@@ -111261,7 +111276,7 @@ var init_registry2 = __esm({
111261
111276
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
111262
111277
  formatVersion: 3,
111263
111278
  contractVersion: "3.13",
111264
- cliVersion: "0.31.7",
111279
+ cliVersion: "0.31.8",
111265
111280
  projectFileUploadBatchSize: 32,
111266
111281
  documentRecords: {
111267
111282
  member: {
@@ -117856,7 +117871,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
117856
117871
  async function main() {
117857
117872
  const args = parseArgs(process.argv.slice(2));
117858
117873
  if (args.command === "--version") {
117859
- console.log("0.31.7");
117874
+ console.log("0.31.8");
117860
117875
  return;
117861
117876
  }
117862
117877
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.31.7",
3
+ "version": "0.31.8",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.31.7 -->
12
+ <!-- reviewed-through-cli: 0.31.8 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.31.7 -->
86
+ <!-- reviewed-through-cli: 0.31.8 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -49,6 +49,11 @@ if (player.Target != null) {
49
49
  return player.Target?.Name ?? "Unknown";
50
50
  ```
51
51
 
52
+ Null propagation remains active across following required accesses in the same
53
+ chain, so `config.Hat?.Head.Idle.Down.Frames` safely returns null when `Hat` is
54
+ null. A descendant that is itself nullable still needs its own `?.`, `??`, `!`,
55
+ or narrowing guard before the next access.
56
+
52
57
  Treat lookup-return values with the same nullable narrowing rules.
53
58
 
54
59
  Let runtime ownership—not body kind—decide whether a write target is Immutable,