Skip to content

graders: refactor to use Static AST Parsing (linkedom, css-tree, ts-morph)#422

Draft
paulirish wants to merge 44 commits into
mainfrom
parser
Draft

graders: refactor to use Static AST Parsing (linkedom, css-tree, ts-morph)#422
paulirish wants to merge 44 commits into
mainfrom
parser

Conversation

@paulirish
Copy link
Copy Markdown
Member

@paulirish paulirish commented Mar 27, 2026

Background & Motivation:
Our Playwright-based graders occasionally suffer from intermittent flakiness and slower execution times when spinning up browsers just to assert basic DOM structure or stylesheet attributes. This PR is a major refactor aimed at heavily utilizing static analysis across our grader suite instead of relying solely on browser automation.

  1. We add three modern parsers:
    • linkedom for blazing fast headless DOM querying.
    • css-tree for CSS AST traversal and @rule analysis.
    • ts-morph for Javascript static evaluation (via a fluent, easy-to-read DOM-like syntax).
  2. Updates AI Generator Directives: Refactors grader-gen.ts to instruct our AI agent to prioritize these static parsers going forward, complete with hardcoded paths to their .d.ts definitions in case the agent needs API hints.
  3. Refactors 11 Legacy Graders: Updated multiple critical graders (Performance, Autofill forms, and UX CSS patterns) over to the new static analysis approach.

The Good:

  • linkedom feels like an absolute, undeniable winner here. Querying document.querySelectorAll() statically on form inputs is awesome.
  • tsmorph i'm also convinced by now. When i put this up as draft, the AST traversals with oxc-parser felt way too verbose and unmaintainable for simple JS assertions. However, after swapping oxc-parser out for ts-morph, the DX and readability are good. good enough that we could keep it as part of the story.

The Questionable:

the css-tree blocks mostly are bad. regex isn't better though. i want a cssom approach i think. and tbh linkedom depends on one. maybe there's something there...

// Static checks
test('Static: Implementation must use JS class toggling for fallback', async () => {
const html = fs.readFileSync(filePath, 'utf-8');
expect(html).toMatch(/classList\.(toggle|add|remove)/);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

imo this one is a big regression in usability.

test('Legacy ::-webkit-scrollbar has width or height defined for visibility', async () => {
const html = fs.readFileSync(filePath, 'utf-8');
// We check for width or height inside the @supports block to ensure it's part of the correctly implemented fallback
const legacySizingMatch = html.match(/@supports\s+not\s*\(\s*scrollbar-color\s*:\s*auto\s*\)\s*{[^}]*::-webkit-scrollbar\s*{[^}]*(width|height)\s*:/s);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

both of these are yuck


test('scrollbar-gutter: stable is used in CSS', async () => {
const html = fs.readFileSync(filePath, 'utf-8');
expect(html).toMatch(/scrollbar-gutter\s*:\s*stable/);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

here the new csstree one aint too bad.

and the two right above seem fine too.

@paulirish paulirish changed the title graders: refactor some to use Static AST Parsing graders: refactor to use Static AST Parsing (linkedom, css-tree, ts-morph) Mar 27, 2026
@paulirish
Copy link
Copy Markdown
Member Author

@micahjo7 wdyt

Comment thread guides/grader-gen.ts

Using template.grader.ts as a framework, write a Playwright test script that directly models the expectations.md requirements.
You should generate both functional and browser tests, with each test containing only one assertion.
You should generate both static tests and browser tests, with each test containing only one assertion.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we could probably just remove this line

@micahjo7
Copy link
Copy Markdown
Collaborator

this sounds like a great idea!

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.

2 participants