Skip to content

fix(amis-core): guard tpl formatDate and date filter against unparseable input#21414

Open
calebcgates wants to merge 1 commit into
baidu:masterfrom
calebcgates:fix/tpl-lodash-format-date-invalid-input
Open

fix(amis-core): guard tpl formatDate and date filter against unparseable input#21414
calebcgates wants to merge 1 commit into
baidu:masterfrom
calebcgates:fix/tpl-lodash-format-date-invalid-input

Conversation

@calebcgates
Copy link
Copy Markdown

What

packages/amis-core/ exposes two adjacent helpers that wrap moment(input, inputFormat).format(format) without an isValid() check:

  1. formatDate in tpl-lodash.ts:24 — the lodash-template helper used as <%= formatDate(data.someField, 'YYYY-MM-DD') %>.
  2. date in filter.ts:148-149 — registered as an amis-formula filter (reachable as ${value | date} in formula expressions) AND re-exported in the lodash template engine as formatTimeStamp: filters.date (see tpl-lodash.ts:50).

The unguarded moment(input, inputFormat).format(format) shape produces two silent failure modes for end users of amis dashboards:

  1. Unparseable input → "Invalid date" rendered to the page. When value is a string that doesn't match inputFormat (typo in a schema, broken upstream API response, etc.), moment(...).format(...) silently returns the literal string "Invalid date" (or "无效日期" under zh-CN locale).
  2. null / undefined value → today's date rendered to the page (only formatDate). This is the moment-undefined foot-gun: moment(undefined) is moment's documented sentinel for "now", and .isValid() returns true. So a typo'd field name (e.g. formatDate(data.creatAt) when the actual field is data.createdAt) silently renders today's date — the user sees what looks like fresh data and never realizes the field is missing. (The date filter defaults inputFormat = 'X', which puts moment in strict-parse mode where undefined is invalid — so this mode only affects formatDate. Bundled the fix anyway for consistency.)

This PR adds the same guard pattern to both helpers:

if (value == null || value === '') {
  return '';                  // typo / missing field → empty string
}
const m = moment(value, inputFormat);
if (!m.isValid()) {
  return String(value);       // unparseable → echo original value
}
return m.format(format);

Why

  • amis is a low-code framework. Schema authors type field names directly into JSON config. Both failure modes above are common in production and currently silent.
  • The fallback String(value) for unparseable input lets the calling template see the actual offending input instead of a hardcoded "Invalid date" string — easier to debug and to wrap in a downstream conditional if needed.
  • The pattern matches the existing convention in the same files:
    • packages/amis-core/src/utils/filter.ts:119-122 (toDate: (input, inputFormat = '') => { const date = moment(input, inputFormat); return date.isValid() ? date.toDate() : undefined; }) — toDate is directly adjacent in the same extendsFilters({ ... }) block to the unguarded date filter.
    • packages/amis-core/src/utils/date.ts:122,142 (both use m.isValid() before formatting).
  • Diff is +28 lines / -4 lines across the two source files plus three new test cases in tpl.test.ts. No public API change. No new dependencies. Existing tests for the formula-syntax happy-path (${value| date} against a valid Unix timestamp, in tpl-builtin.test.ts:103-111) continue to pass.
  • Bundling the two adjacent helpers in one PR (rather than two follow-ups) keeps the bug class closed in one review. The fix shape is identical between them.

How

  • formatDate and the date filter both now short-circuit on value == null || value === '' (returns '') and on !moment(value, inputFormat).isValid() (returns String(value)). The valid path is unchanged for both.
  • Three tests added in packages/amis/__tests__/utils/tpl.test.ts exercising the helpers through the existing filter() entry point — two cover formatDate (new guards + unchanged valid path), one covers formatTimeStamp (the lodash alias for filters.date, with assertions across the null / undefined / '' / unparseable / valid-Unix-timestamp axes).
  • Verified the unparseable-string 'not-a-real-date' no longer surfaces "Invalid date" in either helper's rendered output.

Verification

  • npx jest packages/amis/__tests__/utils/tpl.test.ts — 4/4 passed (1 pre-existing + 3 new).
  • npx jest packages/amis/__tests__/utils/tpl-builtin.test.ts — 46/46 passed, including the existing ${value| date} formula-syntax happy-path test.
  • npx jest packages/amis/__tests__/utils/filter.test.ts — 1/1 passed.
  • npm test --workspace amis-core — 150/150 passed across 14 suites.
  • npm test --workspace amis — 926/927 passed; the single failure (Renderer:inputDate with utc in inputDate.test.tsx) is a pre-existing timezone-sensitive flake that reproduces on master (verified by git stash + rebuild + rerun). It computes moment('2022-08-22 00:00:00').add(moment().utcOffset(), 'm').add(-1, 's').format(...), which is sensitive to the test runner's clock and timezone.
  • NODE_OPTIONS='--max-old-space-size=8192' npm run typecheck — clean.
  • All workspace builds (amis-formulaamis-coreamis-uiamisamis-editor-coreamis-editor) — clean.

@github-actions github-actions Bot added bug Something isn't working doc optimizing document fix labels May 12, 2026
@github-actions
Copy link
Copy Markdown

👍 Thanks for this!
🏷 I have applied any labels matching special text in your issue.

Please review the labels and make any necessary changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working doc optimizing document fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant