-
Notifications
You must be signed in to change notification settings - Fork 213
/
Copy pathanalyze.ts
126 lines (103 loc) · 4 KB
/
analyze.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { workspace, RelativePattern, CancellationToken, Uri, WorkspaceFolder } from 'vscode'
import braces from 'braces'
import { CONFIG_GLOB, CSS_GLOB } from '@tailwindcss/language-server/src/lib/constants'
import { getExcludePatterns } from './exclusions'
export interface SearchOptions {
folders: readonly WorkspaceFolder[]
token: CancellationToken
}
export async function anyWorkspaceFoldersNeedServer({ folders, token }: SearchOptions) {
// An explicit config file setting means we need the server
for (let folder of folders) {
let settings = workspace.getConfiguration('tailwindCSS', folder)
let configFilePath = settings.get('experimental.configFile')
// No setting provided
if (!configFilePath) continue
// Ths config file may be a string:
// A path pointing to a CSS or JS config file
if (typeof configFilePath === 'string') return true
// Ths config file may be an object:
// A map of config files to one or more globs
//
// If we get an empty object the language server will do a search anyway so
// we'll act as if no option was passed to be consistent
if (typeof configFilePath === 'object' && Object.values(configFilePath).length > 0) return true
}
// If any search returns that it needs a workspace then the server needs to be started
// and the remainder of the searches will be cancelled
let searches = folders.map((folder) =>
workspaceFoldersNeedServer({ folder, token }).then((found) => {
if (found) return true
// We use `throw` so we can use Promise.any(…)
throw new Error(DUMMY_ERROR_MESSAGE)
}),
)
const DUMMY_ERROR_MESSAGE = 'Workspace folder not needed'
try {
return await Promise.any(searches)
} catch (err) {
for (let anErr of (err as AggregateError).errors ?? []) {
if (typeof anErr === 'object' && err.message === DUMMY_ERROR_MESSAGE) {
continue
}
console.error(anErr)
}
return false
}
}
export interface FolderSearchOptions {
folder: WorkspaceFolder
token: CancellationToken
}
async function workspaceFoldersNeedServer({ folder, token }: FolderSearchOptions) {
let exclusions = getExcludePatterns(folder).flatMap((pattern) => braces.expand(pattern))
let exclude = `{${exclusions.join(',').replace(/{/g, '%7B').replace(/}/g, '%7D')}}`
// If we find a config file then we need the server
let configs = await workspace.findFiles(
new RelativePattern(folder, `**/${CONFIG_GLOB}`),
exclude,
undefined,
token,
)
if (configs.length > 0) {
return true
}
// If we find a possibly-related stylesheet then we need the server
// The step is done last because it requires reading individual files
// to determine if the server should be started.
//
// This is also, unfortunately, prone to starting the server unncessarily
// in projects that don't use TailwindCSS so we do this one-by-one instead
// of all at once to keep disk I/O low.
let stylesheets = await workspace.findFiles(
new RelativePattern(folder, `**/${CSS_GLOB}`),
exclude,
undefined,
token,
)
for (let file of stylesheets) {
if (await fileMayBeTailwindRelated(file)) {
return true
}
}
}
let HAS_CONFIG = /@config\s*['"]/
let HAS_IMPORT = /@import\s*['"]/
let HAS_TAILWIND = /@tailwind\s*[^;]+;/
let HAS_THEME = /@theme\s*\{/
export async function fileMayBeTailwindRelated(uri: Uri) {
let buffer = await workspace.fs.readFile(uri)
let contents = buffer.toString()
// This is a clear signal that this is Tailwind related in v0–v4
if (HAS_CONFIG.test(contents)) return true
if (uri.path.endsWith('.css')) {
// In v4 these are Tailwind related *in .css files only*
// other stylesheets like lesss, stylus, etc… don't consider these files
if (HAS_THEME.test(contents)) return true
if (HAS_TAILWIND.test(contents)) return true
// @import *might* signal the need for the language server we'll have to
// start it, let it check, and hope we were right.
if (HAS_IMPORT.test(contents)) return true
}
return false
}