Conversation
c4d206f to
70d6111
Compare
| "vscode-uri": "^3.0.7" | ||
| }, | ||
| "devDependencies": { | ||
| "@shopify/theme-check-node": "^3.24.0" |
There was a problem hiding this comment.
I had to move it to a dev-dependency so we can let theme-check-node be dependent on the theme-graph.
Why? Because CLI needs the theme-graph to be able to run the theme-check to detect cross-file CSS usage.
There was a problem hiding this comment.
I think this will blow up the theme-graph binary: https://github.com/Shopify/theme-tools/blob/main/packages/theme-graph/bin/theme-graph#L10
There was a problem hiding this comment.
Im going to use theme-check-common instead of theme-check-node in the binary and move any functions over that doesn't exist. I dont think anyone uses this raw binary anyway.
| fs: NodeFileSystem, | ||
| getSectionSchema: getSectionSchemaFn as any, | ||
| getBlockSchema: getBlockSchemaFn as any, | ||
| getWebComponentDefinitionReference: () => undefined, |
There was a problem hiding this comment.
I don't want to recreate the whole graph because we don't need it for this theme-check, but might silently error out if we add a theme-check that needs this in the future 🤔
(but then again, we would silently fail if getReferences or getDependencies wasn't defined either so maybe not a HUGE deal 🤷)
5a957d9 to
af42032
Compare
| globPattern: '**/*.liquid', | ||
| }, | ||
| { | ||
| globPattern: '**/assets/*.css', |
There was a problem hiding this comment.
Need this or CSS files in assets are going to remain cached from initial load when running themecheck
| "vscode-uri": "^3.0.7" | ||
| }, | ||
| "devDependencies": { | ||
| "@shopify/theme-check-node": "^3.24.0" |
There was a problem hiding this comment.
I think this will blow up the theme-graph binary: https://github.com/Shopify/theme-tools/blob/main/packages/theme-graph/bin/theme-graph#L10
| } | ||
|
|
||
| // 7. Collect ALL CSS classes defined anywhere in the theme | ||
| const allThemeClasses = await getAllThemeCSSClasses(fs, toUri, getCSSClassesForURI); |
There was a problem hiding this comment.
⚡ Performance: getAllThemeCSSClasses is called inside onCodePathEnd, which runs once per checked file. Each invocation does a full recursive directory traversal and reads+parses every .liquid and .css file in the theme. For a theme with N files using class attributes and M total theme files, this means N full-theme scans. Even in the CLI path where getCSSClassesForURI is memoized per-URI, the directory listing itself repeats on every invocation. The same applies to getAssetCSSClasses.
The set of all theme CSS classes is a theme-wide constant. The established pattern in this codebase (e.g., sectionSchemas, blockSchemas, docDefinitions) is to precompute expensive run-scoped data once and share it through injected dependencies. Since this check is enabled by default, the cost scales with every theme that uses class attributes.
Suggestion: Cache the theme-wide class index once per lint run and share it through the injected dependencies, following the pattern used by other expensive run-scoped lookups in the codebase. For example, add getAllThemeCSSClasses?: () => Promise<Set<string>> to the Dependencies interface and have the host compute and cache it once. Alternatively, memoize the result within the check's create() closure.
There was a problem hiding this comment.
The issue isn't really a problem on VSCode Extension since we cache the entire fs. But on CLI, there is partial concern to what you're saying because even though getCSSClassesForURI is memoed, we are iterating over the files individually using recursiveReadDirectory. We could have just made getAllThemeCSSClasses like you suggested which would do it in bulk, but i feel like this breaks the current pattern we are going for here. We always pass a URI for a specific file and it returns its memoized content.
Ill try it out in a subsequent PR though because i still think we take around 18sec of wall-clock time and any gains is nice.
stephanie-shopify
left a comment
There was a problem hiding this comment.
Ran a top hat, found one possible case to look at
Grandparent chain:
Layout → section → snippet: When a layout file defines a CSS class and renders a section, which in turn renders a snippet that uses that class, the check warns that the class is out of scope.
The setup:
- layout/theme.liquid has
{% stylesheet %} .layout-class { ... } {% endstylesheet %}and{{ content_for_layout }} - sections/section.liquid renders
{% render 'deep-snippet' %} - snippets/deep-snippet.liquid uses
<div class="layout-class">
@stephanie-shopify Layout's are not considered direct parents of sections. Their relation is sort of just implied because of You'll see in the side bar, that the parent of
|
|
@graygilmore Thanks for the thorough tophat and review Gray! I've completed the following fixes: I think "Conditional classes" and "Orphaned snippets" might be a little bit too niche. We can always update it once we get feedback on this.
That's a weird bug. I noticed that too. Happens in CLI but not VSCode extension? I wonder if it's a theme-graph issue for orphaned files because the orphan-styles file doesn't have any references in the side panel on VSCode. It should at least have |
8a684b7 to
4a2c9a4
Compare
graygilmore
left a comment
There was a problem hiding this comment.
One more small edge case
Add a new theme check that warns when a CSS class used in an HTML class attribute may be defined outside the scope of the current file, helping catch misplaced styles and dependency issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4a2c9a4 to
7f6fdb4
Compare

🗒️ Summary
Adds a new theme check
ValidScopedCSSClassthat warns when a CSS class used in an HTMLclassattribute may be defined outside the scope of the current file.This helps theme developers catch cases where a CSS class is used in a file but is only defined in a stylesheet tag that isn't accessible from that file's scope — indicating a potential dependency issue or misplaced style.
❓ What is considered "in scope"
A CSS class is considered in scope for a file if it is defined in any of the following:
{% stylesheet %}tag — the file's own stylesheet{% stylesheet %}tags — any file that directly renders this file (via{% render %},{% section %}, etc.), traversed upward through the full ancestor chain. Onlydirecttype references count (notindirectorpreset){% render %}, including that snippet's own snippet descendants (recursive).cssfiles in the theme'sassets/directory are globally in scope👍 What is NOT reported
{{ variable }}) in class attributes are skipped since they can't be statically analyzed🎩 Tophatting
Via VSCode Extension
yarn install && yarn buildfn + F5for some)npx shopify theme init)assets/*.cssfilesRun cli-like environment
node packages/theme-check-node/dist/cli.js [path-to-theme] > result(replace [path-to-theme] with an actual path to theme)resultfile it generated