Skip to content

Commit

Permalink
Use paths relative to opened folder when searching for projects (#1013)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
thecrypticace authored Jul 5, 2024
1 parent d9eaeb8 commit cc2bb07
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 44 deletions.
41 changes: 37 additions & 4 deletions packages/tailwindcss-language-server/src/project-locator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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', [
Expand Down
81 changes: 51 additions & 30 deletions packages/tailwindcss-language-server/src/project-locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<FileEntry>()

Expand All @@ -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
}
}

Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -540,7 +551,8 @@ type ConfigEntry = {

class FileEntry {
content: string | null
deps: Message[] = []
deps: FileEntry[] = []
realpath: string | null

constructor(
public type: 'js' | 'css',
Expand All @@ -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
Expand All @@ -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
Expand Down
28 changes: 19 additions & 9 deletions packages/tailwindcss-language-server/src/tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<ColorInformation[]> {
Expand Down
2 changes: 1 addition & 1 deletion packages/vscode-tailwindcss/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit cc2bb07

Please sign in to comment.