Skip to content

Commit 3277fa3

Browse files
Skip over preprocessor files when looking for v4 configs (#1159)
Tailwind CSS v4.0 is more like a preprocessor and [should not be used with Sass, Less, or Stylus](https://tailwindcss.com/docs/compatibility#sass-less-and-stylus). We don't want to consider these files to be potential CSS configuration files for a v4 project.
1 parent 90fbac0 commit 3277fa3

File tree

3 files changed

+165
-2
lines changed

3 files changed

+165
-2
lines changed

packages/tailwindcss-language-server/src/project-locator.test.ts

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { test } from 'vitest'
1+
import { expect, test } from 'vitest'
22
import * as path from 'node:path'
33
import { ProjectLocator } from './project-locator'
44
import { URL, fileURLToPath } from 'url'
55
import { Settings } from '@tailwindcss/language-service/src/util/state'
66
import { createResolver } from './resolver'
7+
import { css, defineTest, js, json, scss, Storage, TestUtils } from './testing'
78

89
let settings: Settings = {
910
tailwindCSS: {
@@ -227,3 +228,144 @@ testFixture('v4/invalid-import-order', [
227228
content: ['{URL}/package.json'],
228229
},
229230
])
231+
232+
// ---
233+
234+
testLocator({
235+
name: 'Sass files are not detected with v4',
236+
fs: {
237+
'package.json': json`
238+
{
239+
"dependencies": {
240+
"tailwindcss": "^4.0.2"
241+
}
242+
}
243+
`,
244+
'src/app1.scss': scss`
245+
@import 'tailwindcss';
246+
`,
247+
'src/app2.scss': scss`
248+
@use 'tailwindcss';
249+
`,
250+
},
251+
expected: [],
252+
})
253+
254+
testLocator({
255+
name: 'Sass files are detected with v3',
256+
fs: {
257+
'package.json': json`
258+
{
259+
"dependencies": {
260+
"tailwindcss": "^3.4.17"
261+
}
262+
}
263+
`,
264+
'tailwind.admin.config.js': js`
265+
module.exports = {
266+
content: ['./src/**/*.{html,js}'],
267+
}
268+
`,
269+
'src/app.scss': scss`
270+
@config '../tailwind.admin.config.js';
271+
`,
272+
},
273+
expected: [
274+
{
275+
version: '3.4.17',
276+
config: '/tailwind.admin.config.js',
277+
content: ['/src/**/*.{html,js}'],
278+
},
279+
],
280+
})
281+
282+
// ---
283+
284+
function testLocator({
285+
name,
286+
fs,
287+
expected,
288+
settings,
289+
}: {
290+
name: string
291+
fs: Storage
292+
settings?: Partial<Settings>
293+
expected: any[]
294+
}) {
295+
defineTest({
296+
name,
297+
fs,
298+
prepare,
299+
async handle({ search }) {
300+
let projects = await search(settings)
301+
302+
let details = projects.map((project) => ({
303+
version: project.tailwind.isDefaultVersion
304+
? `${project.tailwind.version} (bundled)`
305+
: project.tailwind.version,
306+
config: project.config.path,
307+
content: project.documentSelector
308+
.filter((selector) => selector.priority === 1 /** content */)
309+
.map((selector) => selector.pattern)
310+
.sort(),
311+
selectors: project.documentSelector.map((selector) => selector.pattern).sort(),
312+
}))
313+
314+
expect(details).toMatchObject(expected)
315+
},
316+
})
317+
}
318+
319+
async function prepare({ root }: TestUtils) {
320+
let defaultSettings = {
321+
tailwindCSS: {
322+
files: {
323+
// We want to ignore `node_modules` folders otherwise we'll pick up
324+
// configs from there and we don't want that.
325+
exclude: ['**/node_modules'],
326+
},
327+
},
328+
} as Settings
329+
330+
function adjustPath(filepath: string) {
331+
filepath = filepath.replace(root, '{URL}')
332+
333+
if (filepath.startsWith('{URL}/')) {
334+
filepath = filepath.slice(5)
335+
}
336+
337+
return filepath
338+
}
339+
340+
async function search(overrides?: Partial<Settings>) {
341+
let settings = {
342+
...defaultSettings,
343+
...overrides,
344+
}
345+
346+
let resolver = await createResolver({ root, tsconfig: true })
347+
let locator = new ProjectLocator(root, settings, resolver)
348+
let projects = await locator.search()
349+
350+
// Normalize all the paths for easier testing
351+
for (let project of projects) {
352+
project.folder = adjustPath(project.folder)
353+
project.configPath = adjustPath(project.configPath)
354+
355+
// Config data
356+
project.config.path = adjustPath(project.config.path)
357+
project.config.packageRoot = adjustPath(project.config.packageRoot)
358+
for (let entry of project.config.entries) {
359+
entry.path = adjustPath(entry.path)
360+
}
361+
362+
for (let selector of project.documentSelector ?? []) {
363+
selector.pattern = adjustPath(selector.pattern)
364+
}
365+
}
366+
367+
return projects
368+
}
369+
370+
return { search }
371+
}

packages/tailwindcss-language-server/src/project-locator.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,18 @@ export class ProjectLocator {
159159
if (!tailwind.features.includes('css-at-theme')) {
160160
return null
161161
}
162+
163+
// v4 does not support .sass, .scss, .less, and .styl files as configs
164+
if (requiresPreprocessor(config.path)) {
165+
console.warn(
166+
`The config ${config.path} requires a preprocessor and is not supported by Tailwind CSS v4.0.`,
167+
)
168+
169+
return null
170+
}
162171
}
163172

164-
// Don't boot a project for the CS config if using Tailwind v4
173+
// Don't boot a project for the JS config if using Tailwind v4
165174
if (config.type === 'js' && tailwind.features.includes('css-at-theme')) {
166175
return null
167176
}
@@ -649,6 +658,11 @@ class FileEntry {
649658
}
650659

651660
async resolveImports(resolver: Resolver) {
661+
// Files that require a preprocessor are not processed
662+
if (requiresPreprocessor(this.path)) {
663+
return
664+
}
665+
652666
try {
653667
let result = await resolveCssImports({ resolver, loose: true }).process(this.content, {
654668
from: this.path,
@@ -733,3 +747,9 @@ class FileEntry {
733747
)
734748
}
735749
}
750+
751+
function requiresPreprocessor(filepath: string) {
752+
let ext = path.extname(filepath)
753+
754+
return ext === '.scss' || ext === '.sass' || ext === '.less' || ext === '.styl' || ext === '.pcss'
755+
}

packages/tailwindcss-language-server/src/testing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ async function installDependenciesIn(dir: string) {
102102
}
103103

104104
export const css = dedent
105+
export const scss = dedent
105106
export const html = dedent
106107
export const js = dedent
107108
export const json = dedent

0 commit comments

Comments
 (0)