Skip to content

Commit 7153654

Browse files
feat: beta impl of lint.
1 parent f613eaa commit 7153654

21 files changed

Lines changed: 904 additions & 730 deletions

docs/lint-workers.md

Lines changed: 0 additions & 72 deletions
This file was deleted.

docs/next-steps.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,11 @@ Focused follow-up work for `@knighted/develop`.
5555
- DOM mode still avoids React type graph hydration.
5656
- Suggested implementation prompt:
5757
- "Refactor `src/modules/type-diagnostics.js` to make TypeScript preprocessor parsing (`preProcessFile`) the source of truth for declaration graph discovery in the lazy React type loader. Keep current CDN fallback and lazy hydration semantics. Ensure references from comments are ignored, `*.d.ts`/relative path handling is correct, and candidate fetch ordering minimizes noisy failed requests. Add regression coverage for `global.d.ts` and commented `./user-context` examples. Validate with `npm run lint`, `npm run build:esm`, and targeted React/typecheck Playwright runs."
58+
59+
6. **Deterministic E2E lane in CI**
60+
- Add an integration-style E2E path that uses locally served/pinned copies of CDN runtime dependencies for test execution, while keeping production runtime behavior unchanged.
61+
- Keep the current true CDN-backed E2E path as a separate smoke check, but make the deterministic lane the required gate for pull requests.
62+
- Run this deterministic E2E suite on **every pull request** in CI.
63+
- Ensure the deterministic lane still exercises the same user-facing flows (render, typecheck, lint, diagnostics drawer/button states), only swapping the source of runtime artifacts.
64+
- Suggested implementation prompt:
65+
- "Add a deterministic E2E execution mode for `@knighted/develop` that serves pinned runtime artifacts locally (instead of live CDN fetches) and wire it into CI as a required check on every PR. Keep a separate lightweight CDN-smoke E2E check for real-network coverage. Validate with `npm run lint`, deterministic Playwright PR checks, and one CDN-smoke Playwright run."

playwright/app.spec.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ const setStylesEditorSource = async (page: Page, source: string) => {
2929
await editorContent.fill(source)
3030
}
3131

32+
const runTypecheck = async (page: Page) => {
33+
await ensurePanelToolsVisible(page, 'component')
34+
await page.locator('#typecheck-button').click()
35+
}
36+
37+
const runComponentLint = async (page: Page) => {
38+
await ensurePanelToolsVisible(page, 'component')
39+
await page.locator('#lint-component-button').click()
40+
}
41+
3242
const getCollapseButton = (page: Page, panelName: 'component' | 'styles' | 'preview') =>
3343
page.locator(`#collapse-${panelName}`)
3444

@@ -647,3 +657,174 @@ test('clear all diagnostics removes style compile diagnostics', async ({ page })
647657
/diagnostics-toggle--neutral/,
648658
)
649659
})
660+
661+
test('typecheck success reports ok diagnostics state in button and drawer', async ({
662+
page,
663+
}) => {
664+
await waitForInitialRender(page)
665+
666+
await runTypecheck(page)
667+
668+
await expect(page.locator('#status')).toHaveText('Rendered')
669+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(/diagnostics-toggle--ok/)
670+
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
671+
672+
await page.locator('#diagnostics-toggle').click()
673+
await expect(page.locator('#diagnostics-component')).toContainText(
674+
'No TypeScript errors found.',
675+
)
676+
})
677+
678+
test('typecheck error reports diagnostics count in button and details in drawer', async ({
679+
page,
680+
}) => {
681+
await waitForInitialRender(page)
682+
683+
await setComponentEditorSource(
684+
page,
685+
["const broken: number = 'oops'", 'const App = () => <button>hello</button>'].join(
686+
'\n',
687+
),
688+
)
689+
690+
await runTypecheck(page)
691+
692+
await expect(page.locator('#status')).toHaveText(/Rendered \(Type errors: [1-9]\d*\)/)
693+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
694+
/diagnostics-toggle--error/,
695+
)
696+
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
697+
698+
await page.locator('#diagnostics-toggle').click()
699+
await expect(page.locator('#diagnostics-component')).toContainText('TypeScript found')
700+
await expect(page.locator('#diagnostics-component')).toContainText('TS')
701+
})
702+
703+
test('component lint error reports diagnostics count and details', async ({ page }) => {
704+
await waitForInitialRender(page)
705+
706+
await setComponentEditorSource(
707+
page,
708+
['const unusedValue = 1', 'const App = () => <button>lint me</button>'].join('\n'),
709+
)
710+
711+
await runComponentLint(page)
712+
713+
await expect(page.locator('#status')).toHaveText(/Rendered \(Lint issues: [1-9]\d*\)/)
714+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
715+
/diagnostics-toggle--error/,
716+
)
717+
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
718+
719+
await page.locator('#diagnostics-toggle').click()
720+
await expect(page.locator('#diagnostics-component')).toContainText(
721+
'Biome reported issues.',
722+
)
723+
})
724+
725+
test('component lint ignores unused App View and render bindings', async ({ page }) => {
726+
await waitForInitialRender(page)
727+
728+
await setComponentEditorSource(
729+
page,
730+
[
731+
'function App() { return <button type="button">App</button> }',
732+
'function View() { return <section>View</section> }',
733+
'function render() { return null }',
734+
].join('\n'),
735+
)
736+
737+
await runComponentLint(page)
738+
739+
await page.locator('#diagnostics-toggle').click()
740+
await expect(page.locator('#diagnostics-component')).toContainText(
741+
'No Biome issues found.',
742+
)
743+
744+
await expect(page.locator('#status')).toHaveText('Rendered')
745+
await expect(page.locator('#status')).toHaveClass(/status--neutral/)
746+
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
747+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(/diagnostics-toggle--ok/)
748+
749+
const diagnosticsText = await page.locator('#diagnostics-component').innerText()
750+
expect(diagnosticsText).not.toContain('This variable App is unused')
751+
expect(diagnosticsText).not.toContain('This variable View is unused')
752+
expect(diagnosticsText).not.toContain('This variable render is unused')
753+
expect(diagnosticsText).not.toContain('This function App is unused')
754+
expect(diagnosticsText).not.toContain('This function View is unused')
755+
expect(diagnosticsText).not.toContain('This function render is unused')
756+
})
757+
758+
test('component lint with unresolved issues enters pending diagnostics state while typing', async ({
759+
page,
760+
}) => {
761+
await waitForInitialRender(page)
762+
763+
await setComponentEditorSource(
764+
page,
765+
['const unusedValue = 1', 'const App = () => <button>pending</button>'].join('\n'),
766+
)
767+
768+
await runComponentLint(page)
769+
770+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
771+
/diagnostics-toggle--error/,
772+
)
773+
774+
await setComponentEditorSource(
775+
page,
776+
['const unusedValue = 1', 'const App = () => <button>pending now</button>'].join(
777+
'\n',
778+
),
779+
)
780+
781+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
782+
/diagnostics-toggle--pending/,
783+
)
784+
await expect(page.locator('#diagnostics-toggle')).toHaveAttribute('aria-busy', 'true')
785+
786+
await expect(page.locator('#status')).toHaveText(/Rendered \(Lint issues: [1-9]\d*\)/)
787+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
788+
/diagnostics-toggle--error/,
789+
)
790+
await expect(page.locator('#diagnostics-toggle')).toHaveAttribute('aria-busy', 'false')
791+
})
792+
793+
test('changing css dialect resets diagnostics after lint and typecheck runs', async ({
794+
page,
795+
}) => {
796+
await waitForInitialRender(page)
797+
await ensurePanelToolsVisible(page, 'styles')
798+
799+
await setComponentEditorSource(
800+
page,
801+
[
802+
"const broken: number = 'oops'",
803+
'const unusedValue = 1',
804+
'const App = () => <button>reset me</button>',
805+
].join('\n'),
806+
)
807+
808+
await runTypecheck(page)
809+
await runComponentLint(page)
810+
811+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
812+
/diagnostics-toggle--error/,
813+
)
814+
await expect(page.locator('#diagnostics-toggle')).toHaveText(/Diagnostics \([1-9]\d*\)/)
815+
816+
await page.locator('#style-mode').selectOption('less')
817+
818+
await expect(page.locator('#status')).toHaveText('Rendered')
819+
await expect(page.locator('#status')).toHaveClass(/status--neutral/)
820+
await expect(page.locator('#diagnostics-toggle')).toHaveClass(
821+
/diagnostics-toggle--neutral/,
822+
)
823+
await expect(page.locator('#diagnostics-toggle')).toHaveText('Diagnostics')
824+
825+
await page.locator('#diagnostics-toggle').click()
826+
await expect(page.locator('#diagnostics-component')).toContainText(
827+
'No diagnostics yet.',
828+
)
829+
await expect(page.locator('#diagnostics-styles')).toContainText('No diagnostics yet.')
830+
})

0 commit comments

Comments
 (0)