From cc2bb0788116d640b5cff4e16cb30fb3183a4d2d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 5 Jul 2024 10:22:16 -0400 Subject: [PATCH] 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 --- .../src/project-locator.test.ts | 41 +++++++++- .../src/project-locator.ts | 81 ++++++++++++------- .../tailwindcss-language-server/src/tw.ts | 28 ++++--- packages/vscode-tailwindcss/CHANGELOG.md | 2 +- 4 files changed, 108 insertions(+), 44 deletions(-) diff --git a/packages/tailwindcss-language-server/src/project-locator.test.ts b/packages/tailwindcss-language-server/src/project-locator.test.ts index 0fb67972..34bb73ec 100644 --- a/packages/tailwindcss-language-server/src/project-locator.test.ts +++ b/packages/tailwindcss-language-server/src/project-locator.test.ts @@ -40,6 +40,14 @@ function testFixture(fixture: string, details: any[]) { expect(actual).toEqual(expected) } + + if (detail?.selectors) { + let expected = detail?.selectors.map((path) => path.replace('{URL}', fixturePath)).sort() + + let actual = project.documentSelector.map((selector) => selector.pattern).sort() + + expect(actual).toEqual(expected) + } } expect(projects).toHaveLength(details.length) @@ -90,10 +98,35 @@ testFixture('v4/multi-config', [ ]) testFixture('v4/workspaces', [ - { config: 'packages/admin/app.css' }, - // { config: 'packages/shared/ui.css' }, // Should this be included? - // { config: 'packages/style-export/lib.css' }, // Should this be included? - { config: 'packages/web/app.css' }, + { + config: 'packages/admin/app.css', + selectors: [ + '{URL}/node_modules/tailwindcss/**', + '{URL}/node_modules/tailwindcss/index.css', + '{URL}/node_modules/tailwindcss/theme.css', + '{URL}/node_modules/tailwindcss/utilities.css', + '{URL}/packages/admin/**', + '{URL}/packages/admin/app.css', + '{URL}/packages/admin/package.json', + ], + }, + { + config: 'packages/web/app.css', + selectors: [ + '{URL}/node_modules/tailwindcss/**', + '{URL}/node_modules/tailwindcss/index.css', + '{URL}/node_modules/tailwindcss/theme.css', + '{URL}/node_modules/tailwindcss/utilities.css', + '{URL}/packages/style-export/**', + '{URL}/packages/style-export/lib.css', + '{URL}/packages/style-export/theme.css', + '{URL}/packages/style-main-field/**', + '{URL}/packages/style-main-field/lib.css', + '{URL}/packages/web/**', + '{URL}/packages/web/app.css', + '{URL}/packages/web/package.json', + ], + }, ]) testFixture('v4/auto-content', [ diff --git a/packages/tailwindcss-language-server/src/project-locator.ts b/packages/tailwindcss-language-server/src/project-locator.ts index 6eaf58d6..6a96a967 100644 --- a/packages/tailwindcss-language-server/src/project-locator.ts +++ b/packages/tailwindcss-language-server/src/project-locator.ts @@ -242,20 +242,31 @@ export class ProjectLocator { concurrency: Math.max(os.cpus().length, 1), }) - files = await Promise.all( - files.map(async (file) => { - // Resolve symlinks for all found files - let actualPath = await fs.realpath(file) - - // Ignore network paths on Windows. Resolving relative paths on a - // netshare throws in `enhanced-resolve` :/ - if (actualPath.startsWith('\\') && process.platform === 'win32') { - return normalizePath(file) - } + let realpaths = await Promise.all(files.map((file) => fs.realpath(file))) - return normalizePath(actualPath) - }), - ) + // Remove files that are symlinked yet have an existing file in the list + files = files.filter((normalPath, idx) => { + let realPath = realpaths[idx] + + if (normalPath === realPath) { + return true + } + + // If the file is a symlink, aliased path, network share, etc…; AND + // the realpath is not already in the list of files, then we can add + // the file to the list of files + // + // For example, node_modules in a monorepo setup would be symlinked + // and list both unless you opened one of the directories directly + else if (!files.includes(realPath)) { + return true + } + + return false + }) + + // Make sure Windows-style paths are normalized + files = files.map((file) => normalizePath(file)) // Deduplicate the list of files and sort them for deterministic results // across environments @@ -327,6 +338,9 @@ export class ProjectLocator { // Resolve imports in all the CSS files await Promise.all(imports.map((file) => file.resolveImports())) + // Resolve real paths for all the files in the CSS import graph + await Promise.all(imports.map((file) => file.resolveRealpaths())) + // Create a graph of all the CSS files that might (indirectly) use Tailwind let graph = new Graph() @@ -335,24 +349,21 @@ export class ProjectLocator { let utilitiesPath: string | null = null for (let file of imports) { - graph.add(file.path, file) - - for (let msg of file.deps) { - let importedPath: string = normalizePath(msg.file) - - // Record that `file.path` imports `msg.file` - graph.add(importedPath, new FileEntry('css', importedPath)) + graph.add(file.realpath, file) - graph.connect(file.path, importedPath) + // Record that `file.path` imports `msg.file` + for (let entry of file.deps) { + graph.add(entry.realpath, entry) + graph.connect(file.realpath, entry.realpath) } // Collect the index, theme, and utilities files for manual connection - if (file.path.includes('node_modules/tailwindcss/index.css')) { - indexPath = file.path - } else if (file.path.includes('node_modules/tailwindcss/theme.css')) { - themePath = file.path - } else if (file.path.includes('node_modules/tailwindcss/utilities.css')) { - utilitiesPath = file.path + if (file.realpath.includes('node_modules/tailwindcss/index.css')) { + indexPath = file.realpath + } else if (file.realpath.includes('node_modules/tailwindcss/theme.css')) { + themePath = file.realpath + } else if (file.realpath.includes('node_modules/tailwindcss/utilities.css')) { + utilitiesPath = file.realpath } } @@ -383,7 +394,7 @@ export class ProjectLocator { // And add the config to all their descendants as we need to track updates // that might affect the config / project - for (let child of graph.descendants(root.path)) { + for (let child of graph.descendants(root.realpath)) { child.configs.push(config) } } @@ -540,7 +551,8 @@ type ConfigEntry = { class FileEntry { content: string | null - deps: Message[] = [] + deps: FileEntry[] = [] + realpath: string | null constructor( public type: 'js' | 'css', @@ -559,7 +571,10 @@ class FileEntry { async resolveImports() { try { let result = await resolveCssImports().process(this.content, { from: this.path }) - this.deps = result.messages.filter((msg) => msg.type === 'dependency') + let deps = result.messages.filter((msg) => msg.type === 'dependency') + + // Record entries for each of the dependencies + this.deps = deps.map((msg) => new FileEntry('css', normalizePath(msg.file))) // Replace the file content with the processed CSS this.content = result.css @@ -568,6 +583,12 @@ class FileEntry { } } + async resolveRealpaths() { + this.realpath = normalizePath(await fs.realpath(this.path)) + + await Promise.all(this.deps.map((entry) => entry.resolveRealpaths())) + } + /** * Look for `@config` directives in a CSS file and return the path to the config * file that it points to. This path is (possibly) relative to the CSS file so diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts index 549f5da3..804c6438 100644 --- a/packages/tailwindcss-language-server/src/tw.ts +++ b/packages/tailwindcss-language-server/src/tw.ts @@ -634,9 +634,15 @@ export class TW { } private filterNewWatchPatterns(patterns: string[]) { - let newWatchPatterns = patterns.filter((pattern) => !this.watched.includes(pattern)) - this.watched.push(...newWatchPatterns) - return newWatchPatterns + // Make sure the list of patterns is unique + patterns = Array.from(new Set(patterns)) + + // Filter out any patterns that are already being watched + patterns = patterns.filter((pattern) => !this.watched.includes(pattern)) + + this.watched.push(...patterns) + + return patterns } private async addProject( @@ -792,11 +798,6 @@ export class TW { // to normalize it so that we can compare it properly. fsPath = normalizeDriveLetter(fsPath) - console.debug('[GLOBAL] Matching project to document', { - fsPath, - normalPath, - }) - for (let project of this.projects.values()) { if (!project.projectConfig.configPath) { fallbackProject = fallbackProject ?? project @@ -846,7 +847,16 @@ export class TW { } } - return matchedProject ?? fallbackProject + let project = matchedProject ?? fallbackProject + + if (!project) { + console.debug('[GLOBAL] No matching project for document', { + fsPath, + normalPath, + }) + } + + return project } async onDocumentColor(params: DocumentColorParams): Promise { diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index 79d26034..0232a6a8 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -2,7 +2,7 @@ ## Prerelease -- Nothing yet! +- Use paths relative to opened folder when searching for projects ([#1013](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1013)) ## 0.12.4