Skip to content

Commit cc2bb07

Browse files
Use paths relative to opened folder when searching for projects (#1013)
* Allow testing project selectors * Refactor * Use paths relative to workspace rather than real paths * Use realpaths in CSS import graph * Ignore symlinks of already-present files when searching for configs * Unique watch patterns before adding them * Tweak log * Update changelog
1 parent d9eaeb8 commit cc2bb07

File tree

4 files changed

+108
-44
lines changed

4 files changed

+108
-44
lines changed

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

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ function testFixture(fixture: string, details: any[]) {
4040

4141
expect(actual).toEqual(expected)
4242
}
43+
44+
if (detail?.selectors) {
45+
let expected = detail?.selectors.map((path) => path.replace('{URL}', fixturePath)).sort()
46+
47+
let actual = project.documentSelector.map((selector) => selector.pattern).sort()
48+
49+
expect(actual).toEqual(expected)
50+
}
4351
}
4452

4553
expect(projects).toHaveLength(details.length)
@@ -90,10 +98,35 @@ testFixture('v4/multi-config', [
9098
])
9199

92100
testFixture('v4/workspaces', [
93-
{ config: 'packages/admin/app.css' },
94-
// { config: 'packages/shared/ui.css' }, // Should this be included?
95-
// { config: 'packages/style-export/lib.css' }, // Should this be included?
96-
{ config: 'packages/web/app.css' },
101+
{
102+
config: 'packages/admin/app.css',
103+
selectors: [
104+
'{URL}/node_modules/tailwindcss/**',
105+
'{URL}/node_modules/tailwindcss/index.css',
106+
'{URL}/node_modules/tailwindcss/theme.css',
107+
'{URL}/node_modules/tailwindcss/utilities.css',
108+
'{URL}/packages/admin/**',
109+
'{URL}/packages/admin/app.css',
110+
'{URL}/packages/admin/package.json',
111+
],
112+
},
113+
{
114+
config: 'packages/web/app.css',
115+
selectors: [
116+
'{URL}/node_modules/tailwindcss/**',
117+
'{URL}/node_modules/tailwindcss/index.css',
118+
'{URL}/node_modules/tailwindcss/theme.css',
119+
'{URL}/node_modules/tailwindcss/utilities.css',
120+
'{URL}/packages/style-export/**',
121+
'{URL}/packages/style-export/lib.css',
122+
'{URL}/packages/style-export/theme.css',
123+
'{URL}/packages/style-main-field/**',
124+
'{URL}/packages/style-main-field/lib.css',
125+
'{URL}/packages/web/**',
126+
'{URL}/packages/web/app.css',
127+
'{URL}/packages/web/package.json',
128+
],
129+
},
97130
])
98131

99132
testFixture('v4/auto-content', [

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

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -242,20 +242,31 @@ export class ProjectLocator {
242242
concurrency: Math.max(os.cpus().length, 1),
243243
})
244244

245-
files = await Promise.all(
246-
files.map(async (file) => {
247-
// Resolve symlinks for all found files
248-
let actualPath = await fs.realpath(file)
249-
250-
// Ignore network paths on Windows. Resolving relative paths on a
251-
// netshare throws in `enhanced-resolve` :/
252-
if (actualPath.startsWith('\\') && process.platform === 'win32') {
253-
return normalizePath(file)
254-
}
245+
let realpaths = await Promise.all(files.map((file) => fs.realpath(file)))
255246

256-
return normalizePath(actualPath)
257-
}),
258-
)
247+
// Remove files that are symlinked yet have an existing file in the list
248+
files = files.filter((normalPath, idx) => {
249+
let realPath = realpaths[idx]
250+
251+
if (normalPath === realPath) {
252+
return true
253+
}
254+
255+
// If the file is a symlink, aliased path, network share, etc…; AND
256+
// the realpath is not already in the list of files, then we can add
257+
// the file to the list of files
258+
//
259+
// For example, node_modules in a monorepo setup would be symlinked
260+
// and list both unless you opened one of the directories directly
261+
else if (!files.includes(realPath)) {
262+
return true
263+
}
264+
265+
return false
266+
})
267+
268+
// Make sure Windows-style paths are normalized
269+
files = files.map((file) => normalizePath(file))
259270

260271
// Deduplicate the list of files and sort them for deterministic results
261272
// across environments
@@ -327,6 +338,9 @@ export class ProjectLocator {
327338
// Resolve imports in all the CSS files
328339
await Promise.all(imports.map((file) => file.resolveImports()))
329340

341+
// Resolve real paths for all the files in the CSS import graph
342+
await Promise.all(imports.map((file) => file.resolveRealpaths()))
343+
330344
// Create a graph of all the CSS files that might (indirectly) use Tailwind
331345
let graph = new Graph<FileEntry>()
332346

@@ -335,24 +349,21 @@ export class ProjectLocator {
335349
let utilitiesPath: string | null = null
336350

337351
for (let file of imports) {
338-
graph.add(file.path, file)
339-
340-
for (let msg of file.deps) {
341-
let importedPath: string = normalizePath(msg.file)
342-
343-
// Record that `file.path` imports `msg.file`
344-
graph.add(importedPath, new FileEntry('css', importedPath))
352+
graph.add(file.realpath, file)
345353

346-
graph.connect(file.path, importedPath)
354+
// Record that `file.path` imports `msg.file`
355+
for (let entry of file.deps) {
356+
graph.add(entry.realpath, entry)
357+
graph.connect(file.realpath, entry.realpath)
347358
}
348359

349360
// Collect the index, theme, and utilities files for manual connection
350-
if (file.path.includes('node_modules/tailwindcss/index.css')) {
351-
indexPath = file.path
352-
} else if (file.path.includes('node_modules/tailwindcss/theme.css')) {
353-
themePath = file.path
354-
} else if (file.path.includes('node_modules/tailwindcss/utilities.css')) {
355-
utilitiesPath = file.path
361+
if (file.realpath.includes('node_modules/tailwindcss/index.css')) {
362+
indexPath = file.realpath
363+
} else if (file.realpath.includes('node_modules/tailwindcss/theme.css')) {
364+
themePath = file.realpath
365+
} else if (file.realpath.includes('node_modules/tailwindcss/utilities.css')) {
366+
utilitiesPath = file.realpath
356367
}
357368
}
358369

@@ -383,7 +394,7 @@ export class ProjectLocator {
383394

384395
// And add the config to all their descendants as we need to track updates
385396
// that might affect the config / project
386-
for (let child of graph.descendants(root.path)) {
397+
for (let child of graph.descendants(root.realpath)) {
387398
child.configs.push(config)
388399
}
389400
}
@@ -540,7 +551,8 @@ type ConfigEntry = {
540551

541552
class FileEntry {
542553
content: string | null
543-
deps: Message[] = []
554+
deps: FileEntry[] = []
555+
realpath: string | null
544556

545557
constructor(
546558
public type: 'js' | 'css',
@@ -559,7 +571,10 @@ class FileEntry {
559571
async resolveImports() {
560572
try {
561573
let result = await resolveCssImports().process(this.content, { from: this.path })
562-
this.deps = result.messages.filter((msg) => msg.type === 'dependency')
574+
let deps = result.messages.filter((msg) => msg.type === 'dependency')
575+
576+
// Record entries for each of the dependencies
577+
this.deps = deps.map((msg) => new FileEntry('css', normalizePath(msg.file)))
563578

564579
// Replace the file content with the processed CSS
565580
this.content = result.css
@@ -568,6 +583,12 @@ class FileEntry {
568583
}
569584
}
570585

586+
async resolveRealpaths() {
587+
this.realpath = normalizePath(await fs.realpath(this.path))
588+
589+
await Promise.all(this.deps.map((entry) => entry.resolveRealpaths()))
590+
}
591+
571592
/**
572593
* Look for `@config` directives in a CSS file and return the path to the config
573594
* file that it points to. This path is (possibly) relative to the CSS file so

packages/tailwindcss-language-server/src/tw.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -634,9 +634,15 @@ export class TW {
634634
}
635635

636636
private filterNewWatchPatterns(patterns: string[]) {
637-
let newWatchPatterns = patterns.filter((pattern) => !this.watched.includes(pattern))
638-
this.watched.push(...newWatchPatterns)
639-
return newWatchPatterns
637+
// Make sure the list of patterns is unique
638+
patterns = Array.from(new Set(patterns))
639+
640+
// Filter out any patterns that are already being watched
641+
patterns = patterns.filter((pattern) => !this.watched.includes(pattern))
642+
643+
this.watched.push(...patterns)
644+
645+
return patterns
640646
}
641647

642648
private async addProject(
@@ -792,11 +798,6 @@ export class TW {
792798
// to normalize it so that we can compare it properly.
793799
fsPath = normalizeDriveLetter(fsPath)
794800

795-
console.debug('[GLOBAL] Matching project to document', {
796-
fsPath,
797-
normalPath,
798-
})
799-
800801
for (let project of this.projects.values()) {
801802
if (!project.projectConfig.configPath) {
802803
fallbackProject = fallbackProject ?? project
@@ -846,7 +847,16 @@ export class TW {
846847
}
847848
}
848849

849-
return matchedProject ?? fallbackProject
850+
let project = matchedProject ?? fallbackProject
851+
852+
if (!project) {
853+
console.debug('[GLOBAL] No matching project for document', {
854+
fsPath,
855+
normalPath,
856+
})
857+
}
858+
859+
return project
850860
}
851861

852862
async onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]> {

packages/vscode-tailwindcss/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Prerelease
44

5-
- Nothing yet!
5+
- Use paths relative to opened folder when searching for projects ([#1013](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1013))
66

77
## 0.12.4
88

0 commit comments

Comments
 (0)