tailwint 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +30 -21
- package/dist/lsp.d.ts +14 -4
- package/dist/lsp.js +186 -55
- package/dist/prescan.d.ts +27 -0
- package/dist/prescan.js +76 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
import { resolve, relative } from "path";
|
|
12
12
|
import { readFileSync } from "fs";
|
|
13
13
|
import { glob } from "glob";
|
|
14
|
-
import { startServer, send, notify, shutdown, fileUri, langId, diagnosticsReceived,
|
|
14
|
+
import { startServer, send, notify, shutdown, fileUri, langId, diagnosticsReceived, settledProjects, brokenProjects, warnings, waitForAllProjects, resetState, } from "./lsp.js";
|
|
15
15
|
import { fixFile } from "./edits.js";
|
|
16
|
+
import { prescanCssFiles } from "./prescan.js";
|
|
16
17
|
import { c, setTitle, windTrail, braille, windWave, dots, tick, advanceTick, startSpinner, progressBar, banner, fileBadge, diagLine, rainbowText, celebrationAnimation, } from "./ui.js";
|
|
17
18
|
// Re-export for tests
|
|
18
19
|
export { applyEdits } from "./edits.js";
|
|
@@ -85,9 +86,15 @@ export async function run(options = {}) {
|
|
|
85
86
|
notify("initialized", {});
|
|
86
87
|
stopBoot();
|
|
87
88
|
console.error(` ${c.green}\u2714${c.reset} ${c.dim}language server ready${c.reset} ${windTrail(30)}`);
|
|
88
|
-
//
|
|
89
|
+
// Pre-scan CSS files to predict project count
|
|
90
|
+
const prescan = prescanCssFiles(files);
|
|
91
|
+
// Open found files — triggers the server's project discovery
|
|
89
92
|
const fileContents = new Map();
|
|
90
93
|
const fileVersions = new Map();
|
|
94
|
+
const found = files.length;
|
|
95
|
+
const foundText = `sent ${found} file${found === 1 ? "" : "s"} to lsp`;
|
|
96
|
+
const foundPad = 54 - 2 - foundText.length - 1;
|
|
97
|
+
console.error(` ${c.green}\u2714${c.reset} ${c.dim}${foundText}${c.reset} ${windTrail(foundPad)}`);
|
|
91
98
|
for (const filePath of files) {
|
|
92
99
|
let content;
|
|
93
100
|
try {
|
|
@@ -107,27 +114,29 @@ export async function run(options = {}) {
|
|
|
107
114
|
},
|
|
108
115
|
});
|
|
109
116
|
}
|
|
110
|
-
// Wait for
|
|
111
|
-
setTitle("tailwint ~
|
|
112
|
-
const
|
|
117
|
+
// Wait for all projects to be resolved (settled or broken)
|
|
118
|
+
setTitle("tailwint ~ scanning...");
|
|
119
|
+
const stopScan = startSpinner(() => {
|
|
113
120
|
const received = diagnosticsReceived.size;
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
const resolved = settledProjects + brokenProjects;
|
|
122
|
+
const label = received > 0 ? "scanning" : "initializing";
|
|
123
|
+
setTitle(`tailwint ~ ${label} ${resolved}/${prescan.maxProjects}`);
|
|
124
|
+
const pct = found > 0 ? Math.round((received / found) * 100) : 0;
|
|
117
125
|
const bar = progressBar(pct, 18, true);
|
|
118
|
-
const totalStr = String(
|
|
126
|
+
const totalStr = String(found);
|
|
119
127
|
const recvStr = String(received).padStart(totalStr.length);
|
|
120
|
-
|
|
121
|
-
const usedCols = 2 + 1 + 1 + 20 + 1 + label.length + 3 + 1 + countText.length + 1;
|
|
122
|
-
const waveCols = Math.max(0, 56 - usedCols);
|
|
123
|
-
return ` ${braille()} ${bar} ${c.dim}${label}${dots()}${c.reset} ${c.bold}${recvStr}${c.reset}${c.dim}/${totalStr}${c.reset} ${windTrail(waveCols, tick)}`;
|
|
128
|
+
return ` ${braille()} ${bar} ${c.dim}${label}${dots()}${c.reset} ${c.bold}${recvStr}${c.reset}${c.dim}/${totalStr} scanned${c.reset} ${windTrail(12, tick)}`;
|
|
124
129
|
}, 80);
|
|
125
|
-
await
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
await waitForAllProjects(prescan.predictedRoots, prescan.maxProjects);
|
|
131
|
+
stopScan();
|
|
132
|
+
// Log warnings for broken projects
|
|
133
|
+
for (const w of warnings) {
|
|
134
|
+
console.error(` ${c.yellow}\u26A0${c.reset} ${c.dim}${w}${c.reset}`);
|
|
135
|
+
}
|
|
136
|
+
const scanned = diagnosticsReceived.size;
|
|
137
|
+
const scannedText = `${scanned}/${found} files received`;
|
|
138
|
+
const scannedPad = 54 - 2 - scannedText.length - 1;
|
|
139
|
+
console.error(` ${c.green}\u2714${c.reset} ${c.dim}${scannedText}${c.reset} ${windTrail(scannedPad)}`);
|
|
131
140
|
console.error("");
|
|
132
141
|
// Collect issues
|
|
133
142
|
let totalIssues = 0;
|
|
@@ -149,13 +158,13 @@ export async function run(options = {}) {
|
|
|
149
158
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
150
159
|
setTitle("tailwint \u2714 all clear");
|
|
151
160
|
await celebrationAnimation();
|
|
152
|
-
console.error(` ${c.green}\u2714${c.reset} ${c.bold}${
|
|
161
|
+
console.error(` ${c.green}\u2714${c.reset} ${c.bold}${scanned}${c.reset} files scanned ${c.dim}// ${rainbowText("all clear")} ${c.dim}${elapsed}s${c.reset}`);
|
|
153
162
|
console.error("");
|
|
154
163
|
await shutdown();
|
|
155
164
|
return 0;
|
|
156
165
|
}
|
|
157
166
|
// Summary
|
|
158
|
-
console.error(` ${c.bold}${c.white}${
|
|
167
|
+
console.error(` ${c.bold}${c.white}${scanned}${c.reset} files scanned ${c.dim}//${c.reset} ${c.orange}${c.bold}${conflicts}${c.reset}${c.orange} conflicts${c.reset} ${c.dim}\u2502${c.reset} ${c.yellow}${c.bold}${canonical}${c.reset}${c.yellow} canonical${c.reset}`);
|
|
159
168
|
console.error("");
|
|
160
169
|
// Report
|
|
161
170
|
let fileNum = 0;
|
package/dist/lsp.d.ts
CHANGED
|
@@ -3,12 +3,22 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const diagnosticsReceived: Map<string, any[]>;
|
|
5
5
|
export declare let projectReady: boolean;
|
|
6
|
+
/** Tracking for projectInitialized events */
|
|
7
|
+
export declare let projectInitCount: number;
|
|
8
|
+
export declare let settledProjects: number;
|
|
9
|
+
export declare let brokenProjects: number;
|
|
10
|
+
export declare const warnings: string[];
|
|
6
11
|
/** Reset module state between runs (for programmatic multi-run usage). */
|
|
7
12
|
export declare function resetState(): void;
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Wait for all expected projects to be resolved (settled or broken).
|
|
15
|
+
*
|
|
16
|
+
* @param predictedRoots - Number of CSS files predicted to be project roots
|
|
17
|
+
* @param maxProjects - Upper bound (predictedRoots + predictedNonRoots)
|
|
18
|
+
* @param initTimeoutMs - How long to wait for each projectInitialized event
|
|
19
|
+
* @param debounceMs - Silence window to consider diagnostics "settled"
|
|
20
|
+
*/
|
|
21
|
+
export declare function waitForAllProjects(predictedRoots: number, maxProjects: number, initTimeoutMs?: number, debounceMs?: number): Promise<void>;
|
|
12
22
|
/** Returns a promise that resolves when diagnostics are published for a specific URI. */
|
|
13
23
|
export declare function waitForDiagnostic(uri: string, timeoutMs?: number): Promise<any[]>;
|
|
14
24
|
export declare function startServer(root: string): void;
|
package/dist/lsp.js
CHANGED
|
@@ -61,12 +61,25 @@ const pending = new Map();
|
|
|
61
61
|
export const diagnosticsReceived = new Map();
|
|
62
62
|
export let projectReady = false;
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
|
-
//
|
|
64
|
+
// Project-aware wait state
|
|
65
65
|
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
let
|
|
68
|
-
let
|
|
66
|
+
/** Tracking for projectInitialized events */
|
|
67
|
+
export let projectInitCount = 0;
|
|
68
|
+
export let settledProjects = 0;
|
|
69
|
+
export let brokenProjects = 0;
|
|
70
|
+
let lastInitMs = 0;
|
|
71
|
+
let inBrokenSequence = false;
|
|
72
|
+
let awaitingFirstDiag = false;
|
|
73
|
+
let currentProjectDiagCount = 0;
|
|
74
|
+
export const warnings = [];
|
|
75
|
+
/** Internal waiter state */
|
|
76
|
+
let projectWaitResolve = null;
|
|
77
|
+
let diagDebounceTimer = null;
|
|
78
|
+
let projectInitTimer = null;
|
|
79
|
+
let outerTimer = null;
|
|
69
80
|
const diagWaiters = new Map();
|
|
81
|
+
/** Config for the current wait */
|
|
82
|
+
let waitConfig = { predictedRoots: 0, maxProjects: 0, initTimeoutMs: 5000, debounceMs: 500 };
|
|
70
83
|
/** Reset module state between runs (for programmatic multi-run usage). */
|
|
71
84
|
export function resetState() {
|
|
72
85
|
msgId = 0;
|
|
@@ -76,38 +89,172 @@ export function resetState() {
|
|
|
76
89
|
pending.clear();
|
|
77
90
|
diagnosticsReceived.clear();
|
|
78
91
|
projectReady = false;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
projectInitCount = 0;
|
|
93
|
+
settledProjects = 0;
|
|
94
|
+
brokenProjects = 0;
|
|
95
|
+
lastInitMs = 0;
|
|
96
|
+
inBrokenSequence = false;
|
|
97
|
+
awaitingFirstDiag = false;
|
|
98
|
+
currentProjectDiagCount = 0;
|
|
99
|
+
warnings.length = 0;
|
|
100
|
+
projectWaitResolve = null;
|
|
101
|
+
if (diagDebounceTimer) {
|
|
102
|
+
clearTimeout(diagDebounceTimer);
|
|
103
|
+
diagDebounceTimer = null;
|
|
104
|
+
}
|
|
105
|
+
if (projectInitTimer) {
|
|
106
|
+
clearTimeout(projectInitTimer);
|
|
107
|
+
projectInitTimer = null;
|
|
108
|
+
}
|
|
109
|
+
if (outerTimer) {
|
|
110
|
+
clearTimeout(outerTimer);
|
|
111
|
+
outerTimer = null;
|
|
112
|
+
}
|
|
82
113
|
diagWaiters.clear();
|
|
83
114
|
vscodeSettings = null;
|
|
84
115
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
116
|
+
function cleanupWaitTimers() {
|
|
117
|
+
if (diagDebounceTimer) {
|
|
118
|
+
clearTimeout(diagDebounceTimer);
|
|
119
|
+
diagDebounceTimer = null;
|
|
120
|
+
}
|
|
121
|
+
if (projectInitTimer) {
|
|
122
|
+
clearTimeout(projectInitTimer);
|
|
123
|
+
projectInitTimer = null;
|
|
124
|
+
}
|
|
125
|
+
if (outerTimer) {
|
|
126
|
+
clearTimeout(outerTimer);
|
|
127
|
+
outerTimer = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function finishWait() {
|
|
131
|
+
if (!projectWaitResolve)
|
|
132
|
+
return;
|
|
133
|
+
const resolve = projectWaitResolve;
|
|
134
|
+
projectWaitResolve = null;
|
|
135
|
+
cleanupWaitTimers();
|
|
136
|
+
resolve();
|
|
137
|
+
}
|
|
138
|
+
function isAllResolved() {
|
|
139
|
+
const resolved = settledProjects + brokenProjects;
|
|
140
|
+
return resolved >= waitConfig.maxProjects;
|
|
141
|
+
}
|
|
142
|
+
function startProjectInitTimeout() {
|
|
143
|
+
if (projectInitTimer)
|
|
144
|
+
clearTimeout(projectInitTimer);
|
|
145
|
+
projectInitTimer = setTimeout(() => {
|
|
146
|
+
// Timer fired — either no project init came, or we were waiting for
|
|
147
|
+
// more diagnostics after a single early one. If we got any diagnostics
|
|
148
|
+
// for the current project, settle it before finishing.
|
|
149
|
+
if (currentProjectDiagCount > 0 && !awaitingFirstDiag) {
|
|
150
|
+
settleCurrentProject();
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
finishWait();
|
|
154
|
+
}
|
|
155
|
+
}, waitConfig.initTimeoutMs);
|
|
156
|
+
}
|
|
157
|
+
function onProjectInitialized() {
|
|
158
|
+
projectInitCount++;
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
projectReady = true;
|
|
161
|
+
if (lastInitMs > 0 && (now - lastInitMs) < 500) {
|
|
162
|
+
// Rapid re-init — broken project
|
|
163
|
+
if (!inBrokenSequence) {
|
|
164
|
+
// First rapid init after a healthy one — the previous healthy init was actually broken
|
|
165
|
+
inBrokenSequence = true;
|
|
166
|
+
brokenProjects++;
|
|
167
|
+
warnings.push("A CSS file failed to initialize (likely an @apply referencing an unknown utility). " +
|
|
168
|
+
"That project's files will not receive diagnostics. " +
|
|
169
|
+
"See https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1121");
|
|
170
|
+
// The previous init was counted as starting a healthy project's diagnostic wait.
|
|
171
|
+
// Cancel that wait — this project won't produce diagnostics.
|
|
172
|
+
if (diagDebounceTimer) {
|
|
173
|
+
clearTimeout(diagDebounceTimer);
|
|
174
|
+
diagDebounceTimer = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Additional rapid inits for the same broken project — just update timestamp
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Healthy init — new project starting
|
|
181
|
+
inBrokenSequence = false;
|
|
182
|
+
awaitingFirstDiag = true;
|
|
183
|
+
currentProjectDiagCount = 0;
|
|
184
|
+
// Cancel any pending project-init timeout since we just got a new one
|
|
185
|
+
if (projectInitTimer) {
|
|
186
|
+
clearTimeout(projectInitTimer);
|
|
187
|
+
projectInitTimer = null;
|
|
188
|
+
}
|
|
189
|
+
if (diagDebounceTimer) {
|
|
190
|
+
clearTimeout(diagDebounceTimer);
|
|
191
|
+
diagDebounceTimer = null;
|
|
192
|
+
}
|
|
193
|
+
// Don't start the diagnostic debounce yet — wait for the first diagnostic to arrive.
|
|
194
|
+
// Use the init timeout as the safety net (if no diagnostics arrive at all,
|
|
195
|
+
// this project is effectively broken even though it didn't rapid-fire).
|
|
196
|
+
startProjectInitTimeout();
|
|
197
|
+
}
|
|
198
|
+
lastInitMs = now;
|
|
199
|
+
// Check if broken projects pushed us to completion
|
|
200
|
+
if (isAllResolved()) {
|
|
201
|
+
finishWait();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function settleCurrentProject() {
|
|
205
|
+
settledProjects++;
|
|
206
|
+
if (isAllResolved()) {
|
|
207
|
+
finishWait();
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
startProjectInitTimeout();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function startDiagDebounce() {
|
|
214
|
+
if (diagDebounceTimer)
|
|
215
|
+
clearTimeout(diagDebounceTimer);
|
|
216
|
+
// Cancel the init timeout — we're now in diagnostic-settling mode
|
|
217
|
+
if (projectInitTimer) {
|
|
218
|
+
clearTimeout(projectInitTimer);
|
|
219
|
+
projectInitTimer = null;
|
|
220
|
+
}
|
|
221
|
+
diagDebounceTimer = setTimeout(settleCurrentProject, waitConfig.debounceMs);
|
|
222
|
+
}
|
|
223
|
+
function onDiagnosticReceived() {
|
|
224
|
+
if (!projectWaitResolve)
|
|
225
|
+
return;
|
|
226
|
+
currentProjectDiagCount++;
|
|
227
|
+
if (awaitingFirstDiag) {
|
|
228
|
+
// First diagnostic after a healthy init — don't start the debounce yet.
|
|
229
|
+
// The first diagnostic is often just the CSS entry point, followed by a
|
|
230
|
+
// ~1s pause before the bulk TSX diagnostics arrive. Starting the debounce
|
|
231
|
+
// here would settle too early on large projects.
|
|
232
|
+
awaitingFirstDiag = false;
|
|
233
|
+
}
|
|
234
|
+
else if (currentProjectDiagCount >= 2) {
|
|
235
|
+
// Second diagnostic and beyond — the bulk is flowing, debounce is safe
|
|
236
|
+
startDiagDebounce();
|
|
237
|
+
}
|
|
99
238
|
}
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Wait for all expected projects to be resolved (settled or broken).
|
|
241
|
+
*
|
|
242
|
+
* @param predictedRoots - Number of CSS files predicted to be project roots
|
|
243
|
+
* @param maxProjects - Upper bound (predictedRoots + predictedNonRoots)
|
|
244
|
+
* @param initTimeoutMs - How long to wait for each projectInitialized event
|
|
245
|
+
* @param debounceMs - Silence window to consider diagnostics "settled"
|
|
246
|
+
*/
|
|
247
|
+
export function waitForAllProjects(predictedRoots, maxProjects, initTimeoutMs = 5_000, debounceMs = 500) {
|
|
248
|
+
if (serverDead || maxProjects === 0)
|
|
103
249
|
return Promise.resolve();
|
|
250
|
+
waitConfig = { predictedRoots, maxProjects, initTimeoutMs, debounceMs };
|
|
104
251
|
return new Promise((res) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
252
|
+
projectWaitResolve = res;
|
|
253
|
+
// Start waiting for first project init
|
|
254
|
+
startProjectInitTimeout();
|
|
255
|
+
// Hard outer timeout — never wait longer than this
|
|
256
|
+
const outerMs = initTimeoutMs + (maxProjects * 3000) + 5000;
|
|
257
|
+
outerTimer = setTimeout(finishWait, Math.min(outerMs, 30_000));
|
|
111
258
|
});
|
|
112
259
|
}
|
|
113
260
|
/** Returns a promise that resolves when diagnostics are published for a specific URI. */
|
|
@@ -217,21 +364,12 @@ function processMessages() {
|
|
|
217
364
|
diagWaiters.delete(uri);
|
|
218
365
|
resolve(diags);
|
|
219
366
|
}
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
const resolve = diagTargetResolve;
|
|
223
|
-
diagTargetResolve = null;
|
|
224
|
-
resolve();
|
|
225
|
-
}
|
|
367
|
+
// Notify the project-aware wait system
|
|
368
|
+
onDiagnosticReceived();
|
|
226
369
|
}
|
|
227
370
|
// Tailwind project initialized
|
|
228
371
|
if (msg.method === "@/tailwindCSS/projectInitialized") {
|
|
229
|
-
|
|
230
|
-
if (projectReadyResolve) {
|
|
231
|
-
const resolve = projectReadyResolve;
|
|
232
|
-
projectReadyResolve = null;
|
|
233
|
-
resolve();
|
|
234
|
-
}
|
|
372
|
+
onProjectInitialized();
|
|
235
373
|
}
|
|
236
374
|
}
|
|
237
375
|
}
|
|
@@ -249,18 +387,8 @@ function drainAll(reason) {
|
|
|
249
387
|
p.reject(reason);
|
|
250
388
|
pending.delete(id);
|
|
251
389
|
}
|
|
252
|
-
// Resolve project
|
|
253
|
-
|
|
254
|
-
const r = projectReadyResolve;
|
|
255
|
-
projectReadyResolve = null;
|
|
256
|
-
r();
|
|
257
|
-
}
|
|
258
|
-
// Resolve count-based waiter
|
|
259
|
-
if (diagTargetResolve) {
|
|
260
|
-
const r = diagTargetResolve;
|
|
261
|
-
diagTargetResolve = null;
|
|
262
|
-
r();
|
|
263
|
-
}
|
|
390
|
+
// Resolve project wait (so run() doesn't hang)
|
|
391
|
+
finishWait();
|
|
264
392
|
// Resolve all URI-specific waiters with empty arrays
|
|
265
393
|
for (const [uri, r] of diagWaiters) {
|
|
266
394
|
r([]);
|
|
@@ -323,7 +451,10 @@ export function notify(method, params) {
|
|
|
323
451
|
export async function shutdown() {
|
|
324
452
|
if (serverDead)
|
|
325
453
|
return;
|
|
326
|
-
await
|
|
454
|
+
await Promise.race([
|
|
455
|
+
send("shutdown", {}).catch(() => { }),
|
|
456
|
+
new Promise(r => setTimeout(r, 3000)),
|
|
457
|
+
]);
|
|
327
458
|
notify("exit", {});
|
|
328
459
|
serverDead = true;
|
|
329
460
|
try {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-scan CSS files to predict how many projects the language server will create.
|
|
3
|
+
*
|
|
4
|
+
* analyzeStylesheet() is adapted from tailwindlabs/tailwindcss-intellisense
|
|
5
|
+
* (packages/tailwindcss-language-server/src/version-guesser.ts, MIT licensed).
|
|
6
|
+
*/
|
|
7
|
+
type TailwindVersion = "3" | "4";
|
|
8
|
+
export interface TailwindStylesheet {
|
|
9
|
+
root: boolean;
|
|
10
|
+
versions: TailwindVersion[];
|
|
11
|
+
explicitImport: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function analyzeStylesheet(content: string): TailwindStylesheet;
|
|
14
|
+
export interface PrescanResult {
|
|
15
|
+
/** Total CSS files found */
|
|
16
|
+
totalCssFiles: number;
|
|
17
|
+
/** CSS files predicted to be project roots */
|
|
18
|
+
predictedRoots: number;
|
|
19
|
+
/** CSS files that are Tailwind-related but not roots (could be promoted) */
|
|
20
|
+
predictedNonRoots: number;
|
|
21
|
+
/** CSS files with no Tailwind signals */
|
|
22
|
+
predictedUnrelated: number;
|
|
23
|
+
/** Upper bound on projects the server might create */
|
|
24
|
+
maxProjects: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function prescanCssFiles(files: string[]): PrescanResult;
|
|
27
|
+
export {};
|
package/dist/prescan.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-scan CSS files to predict how many projects the language server will create.
|
|
3
|
+
*
|
|
4
|
+
* analyzeStylesheet() is adapted from tailwindlabs/tailwindcss-intellisense
|
|
5
|
+
* (packages/tailwindcss-language-server/src/version-guesser.ts, MIT licensed).
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
const HAS_V4_IMPORT = /@import\s*['"]tailwindcss(?:\/[^'"]+)?['"]/;
|
|
9
|
+
const HAS_V4_DIRECTIVE = /@(theme|plugin|utility|custom-variant|variant|reference)\s*[^;{]+[;{]/;
|
|
10
|
+
const HAS_V4_FN = /--(alpha|spacing|theme)\(/;
|
|
11
|
+
const HAS_LEGACY_TAILWIND = /@tailwind\s*(base|preflight|components|variants|screens)+;/;
|
|
12
|
+
const HAS_TAILWIND_UTILITIES = /@tailwind\s*utilities\s*[^;]*;/;
|
|
13
|
+
const HAS_TAILWIND = /@tailwind\s*[^;]+;/;
|
|
14
|
+
const HAS_COMMON_DIRECTIVE = /@(config|apply)\s*[^;{]+[;{]/;
|
|
15
|
+
const HAS_NON_URL_IMPORT = /@import\s*['"](?!([a-z]+:|\/\/))/;
|
|
16
|
+
export function analyzeStylesheet(content) {
|
|
17
|
+
if (HAS_V4_IMPORT.test(content)) {
|
|
18
|
+
return { root: true, versions: ["4"], explicitImport: true };
|
|
19
|
+
}
|
|
20
|
+
if (HAS_V4_DIRECTIVE.test(content)) {
|
|
21
|
+
if (HAS_TAILWIND_UTILITIES.test(content)) {
|
|
22
|
+
return { root: true, versions: ["4"], explicitImport: false };
|
|
23
|
+
}
|
|
24
|
+
return { root: false, versions: ["4"], explicitImport: false };
|
|
25
|
+
}
|
|
26
|
+
if (HAS_V4_FN.test(content)) {
|
|
27
|
+
return { root: false, versions: ["4"], explicitImport: false };
|
|
28
|
+
}
|
|
29
|
+
if (HAS_LEGACY_TAILWIND.test(content)) {
|
|
30
|
+
return { root: false, versions: ["3"], explicitImport: false };
|
|
31
|
+
}
|
|
32
|
+
if (HAS_TAILWIND.test(content)) {
|
|
33
|
+
return { root: true, versions: ["4", "3"], explicitImport: false };
|
|
34
|
+
}
|
|
35
|
+
if (HAS_COMMON_DIRECTIVE.test(content)) {
|
|
36
|
+
return { root: false, versions: ["4", "3"], explicitImport: false };
|
|
37
|
+
}
|
|
38
|
+
if (HAS_NON_URL_IMPORT.test(content)) {
|
|
39
|
+
return { root: true, versions: ["4", "3"], explicitImport: false };
|
|
40
|
+
}
|
|
41
|
+
return { root: false, versions: [], explicitImport: false };
|
|
42
|
+
}
|
|
43
|
+
export function prescanCssFiles(files) {
|
|
44
|
+
let predictedRoots = 0;
|
|
45
|
+
let predictedNonRoots = 0;
|
|
46
|
+
let predictedUnrelated = 0;
|
|
47
|
+
for (const filePath of files) {
|
|
48
|
+
if (!filePath.endsWith(".css"))
|
|
49
|
+
continue;
|
|
50
|
+
let content;
|
|
51
|
+
try {
|
|
52
|
+
content = readFileSync(filePath, "utf-8");
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const result = analyzeStylesheet(content);
|
|
58
|
+
if (result.versions.length === 0) {
|
|
59
|
+
predictedUnrelated++;
|
|
60
|
+
}
|
|
61
|
+
else if (result.root) {
|
|
62
|
+
predictedRoots++;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
predictedNonRoots++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const totalCssFiles = predictedRoots + predictedNonRoots + predictedUnrelated;
|
|
69
|
+
return {
|
|
70
|
+
totalCssFiles,
|
|
71
|
+
predictedRoots,
|
|
72
|
+
predictedNonRoots,
|
|
73
|
+
predictedUnrelated,
|
|
74
|
+
maxProjects: predictedRoots + predictedNonRoots,
|
|
75
|
+
};
|
|
76
|
+
}
|