-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
196 lines (174 loc) · 5.89 KB
/
index.js
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
const vscode = require("vscode");
const fs = require("node:fs");
const { readFile } = require("node:fs/promises");
const path = require("node:path");
const { __unstable__loadDesignSystem } = require("tailwindcss");
const { CachedInputFileSystem, ResolverFactory } = require("enhanced-resolve");
const outputChannel = vscode.window.createOutputChannel(
"Tailwind CSS Clojure Class Sorter"
);
const cssResolver = ResolverFactory.createResolver({
fileSystem: new CachedInputFileSystem(fs, 4000),
useSyncFileSystemCalls: true,
extensions: [".css"],
mainFields: ["style"],
conditionNames: ["style"],
});
const moduleResolver = ResolverFactory.createResolver({
fileSystem: new CachedInputFileSystem(fs, 4000),
useSyncFileSystemCalls: true,
extensions: [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"],
mainFields: ["main", "module"],
conditionNames: ["node", "import", "require"],
});
async function loadStylesheet(id, base) {
outputChannel.appendLine(`Loading stylesheet: ${id} from ${base}`);
return new Promise((resolve, reject) => {
cssResolver.resolve({}, base, id, {}, async (err, resolvedPath) => {
if (err || !resolvedPath) {
return reject(err || new Error(`Could not resolve stylesheet: ${id}`));
}
try {
resolve({
content: await readFile(resolvedPath, "utf8"),
base: path.dirname(resolvedPath),
});
} catch (error) {
outputChannel.appendLine(
`Error reading stylesheet: ${id} from ${resolvedPath}: ${error.message}`
);
reject(error);
}
});
});
}
async function loadModule(id, base) {
outputChannel.appendLine(`Loading module: ${id} from ${base}`);
return new Promise((resolve, reject) => {
moduleResolver.resolve({}, base, id, {}, async (error, resolvedPath) => {
if (error) {
outputChannel.appendLine(
`Error resolving module: ${id} from ${base}: ${error.message}`
);
return reject(error);
}
outputChannel.appendLine(`Resolved module ${id} path: ${resolvedPath}`);
try {
const module = await import(resolvedPath);
resolve({ module: module.default ?? module, base });
} catch (error) {
outputChannel.appendLine(
`Error importing module: ${id} from ${resolvedPath}: ${error.message}`
);
reject(error);
}
});
});
}
function sortClasses(designSystem, classes) {
return designSystem
.getClassOrder(classes)
.sort(
([kA, vA], [kB, vB]) =>
(vB === null) - (vA === null) || // sort nulls first
(vA === null && vB === null
? kA.localeCompare(kB) // both null, sort by key
: (vA > vB) - (vA < vB) || // sort by BigInt value
kA.localeCompare(kB)) // values equal, sort by key
)
.map(([k]) => k);
}
async function getDesignSystem() {
const tailwindCssPath = vscode.workspace
.getConfiguration("tailwindCssClojureClassSorter")
.get("tailwindCssPath", "");
const baseWs = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "";
const resolvedCss = tailwindCssPath
? path.resolve(baseWs, tailwindCssPath)
: path.join(baseWs, "node_modules", "tailwindcss", "theme.css");
let cssContent;
try {
cssContent = await readFile(resolvedCss, "utf8");
outputChannel.appendLine(`Read Tailwind CSS from: ${resolvedCss}`);
} catch (error) {
outputChannel.appendLine(`Error reading Tailwind CSS file: ${error}`);
vscode.window.showErrorMessage(
`Failed to read Tailwind CSS file at ${resolvedCss}. Please check the path and permissions. Error: ${error.message}`
);
return null;
}
const baseDir = path.dirname(resolvedCss);
const designSystem = await __unstable__loadDesignSystem(cssContent, {
base: baseDir,
loadStylesheet,
loadModule,
});
outputChannel.appendLine("Successfully loaded Tailwind design system.");
return designSystem;
}
const RULES = [
// outer, inner, delimiter
[':class\\s+"([^"]+)"', "(\\S+)", " "],
['\\^:tw\\s+"([^"]+)"', "(\\S+)", " "],
[":((?:\\.[\\w-]+)+)", "\\.([\\w-]+)", "."],
[
"\\[:[\\w-]*(?:#[\\w-]+)?((?:\\.[\\w-]+)+)(?=[\\s\\]])",
"\\.([\\w-]+)",
".",
],
];
function getWorkspaceEdit(doc, designSystem) {
const edit = new vscode.WorkspaceEdit();
const text = doc.getText();
for (const [_outer, _inner, delim] of RULES) {
const outer = new RegExp(_outer, "g");
const inner = new RegExp(_inner, "g");
let match;
while ((match = outer.exec(text))) {
const [whole, captured] = match;
const classes = Array.from(captured.matchAll(inner), (m) => m[1]);
const prefix = delim === "." ? "." : "";
const sorted = prefix + sortClasses(designSystem, classes).join(delim);
if (sorted === captured) continue;
const offset = match.index + whole.indexOf(captured);
edit.replace(
doc.uri,
new vscode.Range(
doc.positionAt(offset),
doc.positionAt(offset + captured.length)
),
sorted
);
}
}
return edit;
}
class SortTailwindClassesCodeActionProvider {
static kind = vscode.CodeActionKind.Source.append("sortTailwindClasses");
async provideCodeActions(doc) {
if (doc.languageId !== "clojure") return;
const designSystem = await getDesignSystem();
if (!designSystem) return;
const action = new vscode.CodeAction(
"Sort Tailwind Classes",
SortTailwindClassesCodeActionProvider.kind
);
action.edit = getWorkspaceEdit(doc, designSystem);
return [action];
}
}
exports.activate = (context) => {
outputChannel.appendLine(
"Tailwind CSS Clojure Class Sorter extension activated."
);
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
"clojure",
new SortTailwindClassesCodeActionProvider(),
{
providedCodeActionKinds: [SortTailwindClassesCodeActionProvider.kind],
}
)
);
context.subscriptions.push(outputChannel);
};