Skip to content

docs(core): qualify both evaluateExpression references in the registerFunction JSDoc - #5649

Merged
os-sales merged 3 commits into
mainfrom
claude/issue-5580-evaluateexpression-jsdoc
Aug 22, 2026
Merged

docs(core): qualify both evaluateExpression references in the registerFunction JSDoc#5649
os-sales merged 3 commits into
mainfrom
claude/issue-5580-evaluateexpression-jsdoc

Conversation

@os-sales

Copy link
Copy Markdown
Collaborator

Fixes#5580

ExpressionEvaluator.ts exports two entities spelled evaluateExpression — the method on ExpressionEvaluator (bare expression, throws) and the module-level export (context bag, fail-soft). The registerFunction doc block referred to both under the one spelling, four lines apart. Both references are now qualified.

Premise re-verified — and one of the card's claims is measured FALSE

The card's :142 (method) still holds. Its :368 for the module-level export was stale: PR #5578 added ~120 lines of JSDoc above it, so the export now sits at :405. Re-derived on aa3b81062.

The larger correction is to the card's central claim. It states the prose {@link evaluateExpression}"resolves, per TSDoc, to the method. Correct.", and concludes "Not a defect — every statement in that block is true." Asked of the TypeScript checker (checker.getSymbolAtLocation) on the pre-fix source:

@link line 360: "FormulaFunctions.register" -> FormulaFunctions.ts:38 [MethodDeclaration]
@link line 368: "evaluate" -> ExpressionEvaluator.ts:80 [MethodDeclaration]
@link line 370: "evaluateExpression" -> ExpressionEvaluator.ts:405 [FunctionDeclaration]

:405 is the module-level export — the fail-soft one — inside the sentence that calls it "the throwing sibling, and reports 'formatCurrency' is not a function." So the block did contain a false statement; this is a small real defect, not pure imprecision.

Note why the neighbouring link is not evidence of the opposite: {@link evaluate} binds to the method only because no module-level evaluate exists to outrank it. "An unqualified link in a class member resolves to that class's member" is false here, and that is precisely the reading the card relied on.

After the fix:

@link line 371: "ExpressionEvaluator.evaluateExpression" -> ExpressionEvaluator.ts:142 [MethodDeclaration]

The fix

  • Prose link becomes {@link ExpressionEvaluator.evaluateExpression}, which the checker resolves to the MethodDeclaration.
  • The @example's last line is the module-level export (context bag as second parameter; the ${...} wrapper only resolves on the evaluate path), but it sat two lines under calls establishing evaluator. as the receiver, and a .d.ts hover carries no import to disambiguate. It now names the module-level export and shows the import it needs. Both evaluateExpression and ExpressionEvaluator are exported from the package entry, so that import is true as written (verified against the built entry).

Checkable, not asserted — and a declared surface expansion

The dispatch asked that the corrected bindings be made checkable, since a {@link} that binds to the wrong entity is indistinguishable in source from one that binds right. Nothing in this repo resolves link targets: no lint rule, nothing at runtime. The only thing that performs the binding is the checker — which is what an editor hover and the published .d.ts reader both go through.

So this PR adds one file beyond the dispatched surface, declared in the same round on the issue before it was committed:

  • packages/core/src/evaluator/__tests__/registerFunction-jsdoc-links.test.ts

It creates a program over the source file by relative path, so no build artifact sits between an edit and a result. It follows the existing house pattern for compiler-driven pins (packages/core/src/utils/__tests__/freeze-schema.types.test.ts), including that precedent's built-in discrimination leg. Bindings are asserted as kind + owning class rather than line numbers, so the pin does not rot the way the card's :368 did.

The pin can fail — measured on its own mutation leg, twice

In-suite (every run): one case recompiles the same source with the single qualification removed, via an in-memory host overlay, and asserts the link lands back on the module-level FunctionDeclaration.

External ablation: the source was reverted to origin/main and the suite re-run.

ABLATED_VITEST_EXIT=1
x the source carries the qualification, exactly once, and the ablation is a real edit
x "the throwing sibling" resolves to the METHOD, not the module-level export
Tests 2 failed | 3 passed (5)

Two red, and the direction was predicted before the run: the in-suite ablation case stays green under external ablation, because with the source already bare the mutated copy equals the original and the bare link still resolves to the function. It is stated here rather than counted as evidence.

Mutation was confirmed on disk by anchored grep -cF on both the removed and injected text, never an editor exit code — anchors asserted pristine (1 / 0) before mutating, then inverted (0 / 1) after. The script carried trap ... EXIT INT TERM; the tree was verified clean afterwards.

Shipped bytes — the .js prediction is falsified

The prediction under test was: dist/**/*.d.ts moves, dist/**/*.js byte-identical. Measured over all 90 + 90 emitted files, clean-built both legs (dist/andpackages/core/tsconfig.tsbuildinfo cleared — the build info lives outsidedist/, so clearing dist alone would have skipped emit).

artefactpre-fix sha256post-fix sha256moved?
dist/evaluator/ExpressionEvaluator.d.ts8eb97d48…e194d74948e971…0fa3ba08yes — as predicted
dist/evaluator/ExpressionEvaluator.js478dcb80…3a4a4f141fcb9ffa…9cfbd5a1yes — prediction falsified

Exactly one file moved on each leg; the other 89 + 89 are unchanged. The .js moves because this package builds with a bare tsc, which preserves comments in the JS emit — so JSDoc reaches both published artefacts, not only declarations.

The intent behind the prediction (no program semantics change) still holds, and is proven directly rather than by --removeComments, which proves nothing about semantics: the emitted .js delta is 10 changed lines, of which 0 are non-comment lines.

Determinism was cross-checked in both directions: the pre-fix rebuild reproduced the original baseline hashes exactly, and the restore rebuild reproduced the post-fix hashes exactly — so the movement is attributable to this edit, not to build noise, and no mutated artefact was left in dist.

The new test file does not enter the published emit: dist counts stay 90 / 90, with no __tests__ directory and no artefact bearing its name.

Changeset

check-changeset-presence.mjs is the authority and was run rather than inferred from the .d.ts result — it exits 1 before, 0 after. Scored patch, never major (fixed group), matching PR #5578's reasoning on this identical surface: the block is emitted into what npm ships, and the measurement above makes that stronger than #5578 knew, since the JSDoc reaches the .js as well.

Verification

All at 3de60fdcd, the head of this branch. Exit codes captured before any pipe; each gate's own verdict line quoted.

gateresult
pnpm exec vitest run packages/core/CORE_VITEST_EXIT=0Test Files 94 passed (94) · Tests 1950 passed (1950)
pnpm exec vitest run packages/core/src/evaluator/EVALUATOR_VITEST_EXIT=0Test Files 12 passed (12) · Tests 364 passed (364)
new pin aloneVITEST_EXIT=0Tests 5 passed (5)
pnpm --filter @object-ui/core type-checkTYPECHECK_EXIT=0 (tsc --noEmit && tsc -p tsconfig.test.json)
pnpm --filter @object-ui/core lintLINT_EXIT=0✖ 513 problems (0 errors, 513 warnings)
node scripts/check-changeset-presence.mjsEXIT=0✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
node scripts/check-changeset-fixed.mjsEXIT=0✅ All workspace packages are in the changeset fixed group.
node scripts/check-changeset-no-major.mjsEXIT=0✅ No changeset declares a major bump.
node scripts/check-control-bytes.mjsEXIT=0✅ OK (scanned 4697 tracked text file(s); skipped 85 binary)
node scripts/check-package-self-import.mjsEXIT=0✅ No package names itself inside its own src/.

check:self-import is called out deliberately: the new @example line contains import { evaluateExpression } from '@object-ui/core';inside packages/core itself. The gate reports 0 self-import across 16132 module specifier(s), so a specifier in a comment is not counted.

The test suite total moved 93 -> 94 files and 1945 -> 1950 tests against PR #5578's figures — exactly the one file and five cases added here, and nothing else.

Repo-wide pnpm lint / pnpm test are left to CI, and the narrowing is declared rather than assumed. Three pieces, since a narrowing without them is just a skipped run:

  1. Population read from eslint's own resolution, not from a guess about which files count: 186 files for packages/core.
  2. That count read from --format json, not estimated. Both changed source files are in it; the new test file contributes 0 errors, 0 warnings, and the totals (0 errors, 513 warnings) are the pre-existing baseline unchanged.
  3. Root eslint.config.js declares no projectService and no parserOptions.project, so linting is not type-aware and this diff cannot move a verdict in any file it does not itself contain.

check:published-dist was not run to completion — it hit the container's 10-minute foreground cap. Its concern for this diff is answered directly above (dist counts unchanged at 90 / 90, no __tests__ in dist); the full repo-wide scan belongs to CI. Known-broken gauges left untouched: check:eager-closure (exits 2), check:doc-snippet-types (exits 1) — the latter's population is .md / .mdx and package READMEs, so JSDoc @example blocks in .ts are outside it either way.

Scope

Exactly the dispatched surface plus the one declared test file. The fence held: packages/core/src/actions/** (#5611's open draft) is untouched. No behaviour change, no declaration moves — the source diff is confined to a block comment.


Generated by Claude Code

…terFunction JSDoc
`ExpressionEvaluator.ts` exports two entities spelled `evaluateExpression`: the
method on `ExpressionEvaluator` (bare expression, throws) and the module-level
export (context bag, fail-soft). The `registerFunction` doc block referred to
both under the one spelling, four lines apart.
Measured with the TypeScript checker, the unqualified `{@link evaluateExpression}`
did NOT bind to the method: it resolved to the module-level FunctionDeclaration —
the fail-soft one — while the sentence calls it "the throwing sibling". Sibling
`{@link evaluate}` binds to the method only because no module-level `evaluate`
exists to outrank it.
- prose link -> `{@link ExpressionEvaluator.evaluateExpression}`, which the
checker now resolves to the MethodDeclaration.
- the `@example`'s last line is marked as the module-level export and shows the
import it actually needs.
No behaviour change: the edit is confined to a block comment.
Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
…hecker
A `{@link}` that binds to the wrong entity is indistinguishable in source from
one that binds right, and nothing in this repo resolves link targets — no lint
rule, no runtime observation. The checker is the only thing that performs the
binding, and it is what an editor hover and the published `.d.ts` reader both go
through, so these cases ask it directly.
The program is created over the SOURCE file by relative path, so no build
artifact sits between an edit and a result.
Discrimination is built in rather than promised: one case recompiles the same
source with the single qualification removed and asserts the link lands back on
the module-level FunctionDeclaration, so the pin is measured to be capable of
failing on every run.
Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
…tion
Scored `patch` rather than the empty-frontmatter form because the doc block is
emitted into what npm ships: this edit moves both
dist/evaluator/ExpressionEvaluator.d.ts and dist/evaluator/ExpressionEvaluator.js,
the package building with a bare `tsc` that preserves comments in the JS emit.
Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3785.8 KB3867.2 KB
Main entry chunk (gzip)151.6 KB350 KB
Entry fileindex-CftgFQXz.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)11.22KB3.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.34KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)6.35KB2.43KB
auth (index.js)2.77KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.89KB
auth (useIsWorkspaceAdmin.js)3.04KB1.45KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)507.00KB113.72KB
core (index.js)4.51KB1.80KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.33KB
fields (index.js)238.85KB60.13KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.65KB18.32KB
plugin-chatbot (index.js)181.41KB43.22KB
plugin-dashboard (index.js)128.33KB32.93KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)242.16KB60.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.07KB30.43KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.70KB27.17KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.50KB20.68KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)43.66KB14.77KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-sales
os-sales marked this pull request as ready for review August 22, 2026 01:08
@os-sales
os-sales added this pull request to the merge queueAug 22, 2026
Merged via the queue into main with commit 8ebd57fAug 22, 2026
23 checks passed
@os-sales
os-sales deleted the claude/issue-5580-evaluateexpression-jsdoc branch August 22, 2026 01:09
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

registerFunction JSDoc uses one name for two entities — the evaluateExpression method and the module-level export

2 participants

@os-sales@claude