Skip to content

Refuse names that differ only in case in lint:structure - #28

Merged
olehwebdev merged 3 commits into
mainfrom
feature/case-clash-check
Sep 26, 2026
Merged

olehwebdev merged 3 commits into
mainfrom
feature/case-clash-check

Conversation

@olehwebdev

@olehwebdev olehwebdev commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

What and why

The 0.4.0 Release workflow failed to build the installers on macOS and Windows (run 14):

[MISSING_EXPORT] "ActionFields" is not exported by ".../RuleForm/ActionFields.ts".

ActionFields.tsx sat beside actionFields.ts, and RuleNotes.tsx beside ruleNotes.ts. macOS and Windows ignore case, so import './ActionFields' found actionFields.ts first. CI builds only on Linux, so nothing caught it until the release. olehwebdev/console-editor#27 fixes those two pairs. This PR stops the mistake from coming back.

npm run lint:structure now also walks src, scripts and test and refuses, within one folder:

  • two names that differ only in case (Logo.svg, logo.svg). These can't even coexist in a checkout on macOS or Windows.
  • two modules an import without an extension reaches by names that differ only in case. A module is a folder, or a file with one of Vite's resolve.extensions (.mjs .js .mts .ts .jsx .tsx .json). Examples: ActionFields.tsx beside actionFields.ts, or a folder foo/ beside Foo.ts.

It passes:

  • Markdown.tsx beside markdown.css: a stylesheet is imported by its whole name, so the two never meet.
  • Names spelled alike (Foo.ts beside Foo.tsx): no case is involved, so every file system treats them the same.

The check runs wherever lint:structure already does: CI, the pre-push hook, and the agents' stop hook. It needs no new wiring.

Code:

  • scripts/structure/:
    • caseClashes.ts: the pure check for one folder's entries;
    • lookupNames.ts: the names a lookup reaches an entry by;
    • findCaseClashes.ts: the walk;
    • constants.ts: the checked roots and module extensions.
  • check-structure.ts adds the walk's findings to its report.

Docs: CLAUDE.md and CONTRIBUTING (the rule, and the lint:structure comment), the SPEC's test table, and the code-structure skill's naming step.

The branch also carries #27's rename commit (8acf75a, the same change). Without it, lint:structure would fail on main, which still has both pairs. Git merges the identical rename cleanly, whichever PR merges first.

How it was tested

  • On main's tree, before the rename, npm run lint:structure reports exactly the two pairs:
    src/renderer/src/features/rule/edit/ui/RuleForm  no case clashes: ActionFields.tsx, actionFields.ts: macOS and Windows ignore case, so they take one for the other (an import may load the wrong one): rename one
    src/renderer/src/features/rule/edit/ui/RuleForm  no case clashes: RuleNotes.tsx, ruleNotes.ts: …
    
    With the rename it passes (Code structure: 2238 files OK.).
  • New unit tests in test/unit/checkStructure.test.ts:
    • the release's clashing pairs fail, and so do a folder beside a module and .json beside .js;
    • two non-module names that differ only in case fail, reported once;
    • the stylesheet case, names spelled alike, and the renamed files pass;
    • a temporary tree is walked: clashes in src and test are named by folder, docs/ is ignored, and the tree passes once they're removed.
  • Checks: npm run typecheck, lint:fsd, lint:structure, lint, lint:unused, lint:duplicates and lint:secrets pass.
  • Tests: npm test passes (1,175). xvfb-run -a npm run test:e2e passes (109).
    • The first end-to-end run had one timing failure, in inspect.e2e.test.ts › "opens the original read-only…": a poll with vitest's default 1-second timeout, waiting for the bundle to open.
    • That test is untouched here. It passed 3 of 3 on its own and in a full rerun.

Checklist

  • Comes from a git flow branch (feature/…, bugfix/…) into main, and does one thing
  • npm run typecheck, npm run lint:fsd, npm run lint:structure, npm run lint, npm run lint:unused, npm run lint:duplicates, npm run lint:secrets, npm test and npm run test:e2e pass
  • docs/SPEC.md describes any behaviour this changes (its test table lists the new rule)
  • User-visible changes are noted under [Unreleased] in CHANGELOG.md (none: tooling only)

ActionFields.tsx sat beside actionFields.ts, and RuleNotes.tsx beside
ruleNotes.ts. On macOS and Windows, whose file systems ignore case,
`./ActionFields` found actionFields.ts first, which exports no component,
so the Release workflow's build failed there while Linux (and CI) passed.

actionFields.ts becomes ruleActionFields.ts, after RULE_ACTION_FIELDS, and
ruleNotes.ts becomes ruleNotesFor.ts, its function ruleNotesFor.
macOS and Windows ignore case, so ActionFields.tsx beside actionFields.ts
made `./ActionFields` load the helper there, and the 0.4.0 installers
failed to build while Linux, and so CI, passed. lint:structure now walks
src, scripts and test and refuses, in one folder, two names that differ
only in case, or two modules (folders, or files with one of Vite's
resolve extensions) an import without an extension reaches by names that
do. A stylesheet beside its component (Markdown.tsx, markdown.css) is
imported by its whole name and passes.

CLAUDE.md, CONTRIBUTING, the SPEC and the code-structure skill say so.
@olehwebdev
olehwebdev merged commit caf681c into main Sep 26, 2026
1 check passed
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.

1 participant