Skip to content

Conversation

@mathpirate
Copy link
Contributor

@mathpirate mathpirate commented Oct 8, 2025

Not ready for review

Builds on fix/detect-actual-default-exports


Summary by cubic

Fix schema generation for mapped types (Record and inline mapped types) and correct default-export detection in the runner to prevent invalid re-exports and permissive schemas.

  • Bug Fixes

    • Resolve mapped type property types via checker.getPropertyOfType/getTypeOfSymbol; no more any/true in schemas.
    • Keep TS lib mapped types (Record, Partial, Pick, Required, etc.) inlined by detecting lib.*.d.ts origins; no $defs hoisting.
    • Parse AST to detect real default exports in the runner (export default and export { ... as default }).
    • Add tests and fixtures for Record<union, T>, inline mapped types, Partial/Pick/Required, and nested Record.
  • Dependencies

    • Add typescript to packages/runner imports and lockfile.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 9 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="packages/runner/src/harness/engine.ts">

<violation number="1" location="packages/runner/src/harness/engine.ts:257">
hasDefaultExport misses default-exported function/class declarations, so files with `export default function` lose their default export when re-mapped and Engine.run fails.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.


// Checks if a source file has a default export by parsing its AST.
// Detects both `export default ...` and `export { ... as default }` patterns.
function hasDefaultExport(source: Source): boolean {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasDefaultExport misses default-exported function/class declarations, so files with export default function lose their default export when re-mapped and Engine.run fails.

Prompt for AI agents
Address the following comment on packages/runner/src/harness/engine.ts at line 257:

<comment>hasDefaultExport misses default-exported function/class declarations, so files with `export default function` lose their default export when re-mapped and Engine.run fails.</comment>

<file context>
@@ -251,6 +252,36 @@ function computeId(program: Program): string {
 
+// Checks if a source file has a default export by parsing its AST.
+// Detects both `export default ...` and `export { ... as default }` patterns.
+function hasDefaultExport(source: Source): boolean {
+  const sourceFile = ts.createSourceFile(
+    source.name,
</file context>

✅ Addressed in 5f0a842

]
}
},
"required": [
Copy link
Contributor

@ubik2 ubik2 Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if we should have "additionalProperties": false on these. In the TypeScript world, we don't let you assign an object with extra properties to these.

Edit: this isn't meant to be something that should be changed -- just not sure what the best behavior is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a cool question that caused me to learn some things about typescript, thank you :)

here's an amalgamation of some inputs from claude that make sense to me as a set of takeaways:

Arguments for "additionalProperties": false:

  • Stricter validation matches TypeScript's intent
  • Catches accidental extra properties (typos, outdated fields)
  • More defensive/explicit about the contract

Arguments against (current behavior):

  • TypeScript's excess property checking only applies to object literals in direct assignments
 type Settings = Record<"theme" | "language", string>;
 const valid: Settings = { theme: "dark", language: "en" };           // ✓
 const extra: Settings = { theme: "dark", language: "en", foo: "x" }; // ✗ TypeScript error
 // ...but this is valid TypeScript:
 const obj = { theme: "dark", language: "en", foo: "x" };
 const settings: Settings = obj; // ✓ No error! TypeScript's structural typing
  • JSON Schema is often used for runtime validation where data comes from external sources
  • Being more permissive at runtime matches TypeScript's structural nature
  • The schema generator has been following a pattern of only adding additionalProperties: false when TypeScript explicitly forbids extra properties (not just excess
    property checking)

What do you think the validation use case is here? Are these schemas primarily for:

  • Type validation at API boundaries (stricter might be better)
  • Internal type checking (current behavior is fine)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging this for now but happy to keep chatting about this topic and can easily make whatever changes we decide on in a follow-up!

@mathpirate mathpirate self-assigned this Oct 8, 2025
mathpirate and others added 8 commits October 8, 2025 15:06
Mapped types (like Record<K, V> and inline mapped types) have synthetic
properties without explicit declarations. Updated safeGetPropertyType()
to use checker.getPropertyOfType() + getTypeOfSymbol() to resolve these
property types correctly, preventing them from falling back to 'any'
(which generates permissive 'true' schemas).

Added fixture test for inline mapped types to prevent regression.

Note: record-mapped-types.test.ts is included but currently fails due to
Record<K,V> being incorrectly hoisted to $defs. This will be fixed in
the next commit (Phase 2: native type detection).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implemented lib type detection to prevent built-in TypeScript mapped
types (Record, Partial, Pick, Required, etc.) from being hoisted to
$defs. These types are defined in TypeScript's lib files and should be
inlined rather than referenced.

Detection strategy:
- Check if type symbol/alias/target declarations come from lib files
- Match patterns: lib.*.d.ts, ES*.d.ts, or /typescript/*.d.ts paths
- Check symbol, target symbol (for type references), and alias symbol

Added fixture tests for Record<K, V> to prevent regression.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…re parametrized generics rather than whether they're library functions
Cleanup:
- Reverted aliasSymbol extraction to keep diff minimal
- Reverted targetSymbol scope to original tighter scope
- Both changes maintain exact same behavior, just cleaner

New edge case tests:
- Record<string, T> with additionalProperties (passes)
- Generic types with default parameters (FAILING - needs investigation)
- Conditional types resolving to Records (FAILING - needs investigation)

Investigation notes for next session:
- Box<number> is generating {value: string} instead of {value: number}
- Conditional MaybeRecord<"foo"|"bar"> generates anyOf (may be correct due to distributive conditionals)
- Need to debug why Box<number> loses its type argument

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…eGetPropertyType

  - Compare parent vs declaration types to detect generic instantiation (e.g., Box<number>)
  - Strip | undefined from optional properties using type flags instead of string matching
  - Handle Partial<T> and other mapped types correctly
  - Fix recursion tests by preferring declaration type for optional properties
@mathpirate mathpirate force-pushed the fix/record-mapped-type-schemas branch from 0a52b71 to f68116e Compare October 8, 2025 22:06
@mathpirate mathpirate merged commit 88fa2ad into main Oct 8, 2025
8 checks passed
@mathpirate mathpirate deleted the fix/record-mapped-type-schemas branch October 8, 2025 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants