@master/eslint-plugin-css 0.0.0-site.1793659883.g69c9aebcf628
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/README.md +291 -0
- package/dist/configs/base.d.ts +3 -0
- package/dist/configs/base.js +4 -0
- package/dist/configs/recommended.d.ts +3 -0
- package/dist/configs/recommended.js +12 -0
- package/dist/create-rule.d.ts +5 -0
- package/dist/create-rule.js +4 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +15 -0
- package/dist/internal/src/workspace-directories.js +67 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +28 -0
- package/dist/rules/no-conflicting-classes.d.ts +4 -0
- package/dist/rules/no-conflicting-classes.js +39 -0
- package/dist/rules/no-invalid-classes.d.ts +4 -0
- package/dist/rules/no-invalid-classes.js +41 -0
- package/dist/rules/no-unapproved-raw-values.d.ts +8 -0
- package/dist/rules/no-unapproved-raw-values.js +63 -0
- package/dist/rules/prefer-canonical-classes.d.ts +5 -0
- package/dist/rules/prefer-canonical-classes.js +48 -0
- package/dist/rules/prefer-component-classes.d.ts +4 -0
- package/dist/rules/prefer-component-classes.js +45 -0
- package/dist/rules/sort-classes.d.ts +4 -0
- package/dist/rules/sort-classes.js +38 -0
- package/dist/settings-schema.d.ts +4 -0
- package/dist/settings-schema.js +26 -0
- package/dist/settings.d.ts +16 -0
- package/dist/settings.js +7 -0
- package/dist/utils/define-source-visitors.d.ts +13 -0
- package/dist/utils/define-source-visitors.js +51 -0
- package/dist/utils/define-visitors.d.ts +11 -0
- package/dist/utils/define-visitors.js +102 -0
- package/dist/utils/dependency-stamp.d.ts +10 -0
- package/dist/utils/dependency-stamp.js +27 -0
- package/dist/utils/is-same-or-child-path.d.ts +2 -0
- package/dist/utils/is-same-or-child-path.js +7 -0
- package/dist/utils/javascript-literal.d.ts +3 -0
- package/dist/utils/javascript-literal.js +51 -0
- package/dist/utils/master-css.d.ts +3 -0
- package/dist/utils/master-css.js +4 -0
- package/dist/utils/report-lint-diagnostics.d.ts +20 -0
- package/dist/utils/report-lint-diagnostics.js +54 -0
- package/dist/utils/resolve-class-node.d.ts +26 -0
- package/dist/utils/resolve-class-node.js +106 -0
- package/dist/utils/resolve-context.d.ts +15 -0
- package/dist/utils/resolve-context.js +177 -0
- package/dist/utils/source-tooling.d.ts +7 -0
- package/dist/utils/source-tooling.js +40 -0
- package/dist/utils/vue-literal.d.ts +8 -0
- package/dist/utils/vue-literal.js +61 -0
- package/dist/utils/with-context-release.d.ts +3 -0
- package/dist/utils/with-context-release.js +15 -0
- package/dist/utils/with-visit-class-node.d.ts +6 -0
- package/dist/utils/with-visit-class-node.js +50 -0
- package/dist/utils/workspace-cache.d.ts +11 -0
- package/dist/utils/workspace-cache.js +30 -0
- package/package.json +50 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { encodeJavaScriptLiteral, javascriptLiteralRange } from "./javascript-literal.js";
|
|
2
|
+
import { resolveVueLiteral } from "./vue-literal.js";
|
|
3
|
+
// src/utils/resolve-class-node.ts
|
|
4
|
+
function resolveClassNode(node, context, lintSession) {
|
|
5
|
+
const { sourceCode } = context;
|
|
6
|
+
let value = null;
|
|
7
|
+
let raw = null;
|
|
8
|
+
let start = null;
|
|
9
|
+
let end = null;
|
|
10
|
+
let unescape = false;
|
|
11
|
+
const javascript = node.type === "TemplateElement" || node.type === "Literal" && node.parent?.type !== "JSXAttribute";
|
|
12
|
+
const html = javascript ? resolveVueLiteral(node, sourceCode, lintSession) : void 0;
|
|
13
|
+
switch (node.type) {
|
|
14
|
+
case "Literal":
|
|
15
|
+
value = node.value;
|
|
16
|
+
raw = html?.raw ?? node.raw;
|
|
17
|
+
start = node.range[0];
|
|
18
|
+
end = node.range[1];
|
|
19
|
+
break;
|
|
20
|
+
case "VLiteral":
|
|
21
|
+
value = node.value;
|
|
22
|
+
raw = sourceCode.getText(node);
|
|
23
|
+
start = node.range[0];
|
|
24
|
+
end = node.range[1];
|
|
25
|
+
break;
|
|
26
|
+
case "TextAttribute":
|
|
27
|
+
value = node.value;
|
|
28
|
+
start = node.valueSpan.fullStart.offset;
|
|
29
|
+
end = node.valueSpan.end.offset;
|
|
30
|
+
raw = sourceCode.getText({
|
|
31
|
+
...node,
|
|
32
|
+
range: [start, end]
|
|
33
|
+
});
|
|
34
|
+
break;
|
|
35
|
+
case "SvelteLiteral":
|
|
36
|
+
value = node.value;
|
|
37
|
+
raw = sourceCode.getText(node);
|
|
38
|
+
start = node.range[0];
|
|
39
|
+
end = node.range[1];
|
|
40
|
+
break;
|
|
41
|
+
case "TemplateElement":
|
|
42
|
+
if (node.parent.expressions.length) return;
|
|
43
|
+
value = node.value.cooked;
|
|
44
|
+
start = node.range[0];
|
|
45
|
+
end = node.range[1];
|
|
46
|
+
raw = html?.raw ?? sourceCode.text.slice(start, end);
|
|
47
|
+
unescape = "`";
|
|
48
|
+
break;
|
|
49
|
+
default: return;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value !== "string") return;
|
|
52
|
+
const rawMatchesSource = raw === sourceCode.text.slice(start, end);
|
|
53
|
+
let delimiterOffset = 0;
|
|
54
|
+
if (/^(['"`])([\s\S]*)\1$/.test(raw)) {
|
|
55
|
+
unescape = raw[0];
|
|
56
|
+
const contentRange = html?.sourceRange(1, raw.length - 1);
|
|
57
|
+
raw = raw.slice(1, -1);
|
|
58
|
+
delimiterOffset = 1;
|
|
59
|
+
start = contentRange?.[0] ?? start + 1;
|
|
60
|
+
end = contentRange?.[1] ?? end - 1;
|
|
61
|
+
}
|
|
62
|
+
const literalRange = javascript ? javascriptLiteralRange(raw, value) : void 0;
|
|
63
|
+
const sourceRange = literalRange && ((a, b) => {
|
|
64
|
+
const [first, last] = literalRange(a, b);
|
|
65
|
+
if (!html) return [first, last];
|
|
66
|
+
const [sourceStart, sourceEnd] = html.sourceRange(delimiterOffset + first, delimiterOffset + last);
|
|
67
|
+
return [sourceStart - start, sourceEnd - start];
|
|
68
|
+
});
|
|
69
|
+
if (javascript && !sourceRange) return;
|
|
70
|
+
const analysisText = javascript ? value : raw;
|
|
71
|
+
const nodes = lintSession.tokenizeClassList(analysisText, javascript ? false : unescape).map((item) => {
|
|
72
|
+
const [mappedStart, mappedEnd] = sourceRange?.(item.range.start, item.range.end) ?? [item.range.start, item.range.end];
|
|
73
|
+
const startOffset = start + mappedStart;
|
|
74
|
+
const endOffset = start + mappedEnd;
|
|
75
|
+
return {
|
|
76
|
+
type: "class",
|
|
77
|
+
value: item.token,
|
|
78
|
+
raw: sourceCode.text.slice(startOffset, endOffset),
|
|
79
|
+
range: [startOffset, endOffset],
|
|
80
|
+
loc: {
|
|
81
|
+
start: sourceCode.getLocFromIndex(startOffset),
|
|
82
|
+
end: sourceCode.getLocFromIndex(endOffset)
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
const classNodes = nodes.filter((node) => node.type === "class");
|
|
87
|
+
return {
|
|
88
|
+
nodes,
|
|
89
|
+
start,
|
|
90
|
+
end,
|
|
91
|
+
raw: sourceCode.text.slice(start, end),
|
|
92
|
+
value,
|
|
93
|
+
unescape,
|
|
94
|
+
classNodes,
|
|
95
|
+
classValues: classNodes.map((node) => node.value),
|
|
96
|
+
analysisText,
|
|
97
|
+
sourceRange,
|
|
98
|
+
encodeReplacement: javascript ? (text) => {
|
|
99
|
+
const newline = raw.match(/\r\n|\r|\n/)?.[0] ?? "\n";
|
|
100
|
+
const replacement = encodeJavaScriptLiteral(text, unescape || "\"", newline);
|
|
101
|
+
return html?.encode(replacement) ?? replacement;
|
|
102
|
+
} : void 0,
|
|
103
|
+
canFix: !javascript || Boolean(html) || rawMatchesSource
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export { resolveClassNode as default };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MasterCSSToolingSession } from "@master/css-tooling";
|
|
2
|
+
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
|
|
3
|
+
declare function resolveContext(context: RuleContext<any, any[]>): {
|
|
4
|
+
settings: {
|
|
5
|
+
classAttributes: readonly string[];
|
|
6
|
+
classFunctions: readonly string[];
|
|
7
|
+
classDeclarations: readonly string[];
|
|
8
|
+
ignoredKeys: readonly string[];
|
|
9
|
+
manifest: any;
|
|
10
|
+
};
|
|
11
|
+
options: any;
|
|
12
|
+
tooling: MasterCSSToolingSession;
|
|
13
|
+
release(): void;
|
|
14
|
+
};
|
|
15
|
+
export { resolveContext as default };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import settings from "../settings.js";
|
|
2
|
+
import { defaultManifest } from "./master-css.js";
|
|
3
|
+
import isSameOrChildPath from "./is-same-or-child-path.js";
|
|
4
|
+
import { isDependencyCurrent, stampDependency } from "./dependency-stamp.js";
|
|
5
|
+
import { acquireWorkspaceSnapshot, releaseWorkspaceSnapshot } from "./workspace-cache.js";
|
|
6
|
+
import { createSourceTooling } from "./source-tooling.js";
|
|
7
|
+
import { loadProjectManifestSync } from "@master/css-compiler/project/sync";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { createToolingSessionSync } from "@master/css-tooling/node";
|
|
11
|
+
// src/utils/resolve-context.ts
|
|
12
|
+
const sourceCaches = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
const cssCaches = [];
|
|
14
|
+
const CACHE_IDLE_DISPOSE_DELAY = 1e3;
|
|
15
|
+
function disposeCSSCache(cache) {
|
|
16
|
+
if (cache.disposeTimer) clearTimeout(cache.disposeTimer);
|
|
17
|
+
cache.disposeTimer = void 0;
|
|
18
|
+
cache.tooling.dispose();
|
|
19
|
+
const index = cssCaches.indexOf(cache);
|
|
20
|
+
if (index !== -1) cssCaches.splice(index, 1);
|
|
21
|
+
}
|
|
22
|
+
function scheduleCSSCacheDisposal(cache) {
|
|
23
|
+
if (cache.references || cache.disposeTimer) return;
|
|
24
|
+
cache.disposeTimer = setTimeout(() => {
|
|
25
|
+
cache.disposeTimer = void 0;
|
|
26
|
+
if (!cache.references) disposeCSSCache(cache);
|
|
27
|
+
}, CACHE_IDLE_DISPOSE_DELAY);
|
|
28
|
+
cache.disposeTimer.unref?.();
|
|
29
|
+
}
|
|
30
|
+
function retainCSSCache(cache) {
|
|
31
|
+
if (cache.disposeTimer) clearTimeout(cache.disposeTimer);
|
|
32
|
+
cache.disposeTimer = void 0;
|
|
33
|
+
cache.references++;
|
|
34
|
+
}
|
|
35
|
+
function getContextFilename(context) {
|
|
36
|
+
const filename = context.physicalFilename || context.filename;
|
|
37
|
+
if (!filename || filename.startsWith("<")) return;
|
|
38
|
+
return path.isAbsolute(filename) ? filename : path.resolve(context.cwd, filename);
|
|
39
|
+
}
|
|
40
|
+
function getWorkspaceSnapshot(sourceCache, cwd) {
|
|
41
|
+
let snapshot = sourceCache.workspaces.get(cwd);
|
|
42
|
+
if (!snapshot) {
|
|
43
|
+
snapshot = acquireWorkspaceSnapshot(cwd);
|
|
44
|
+
sourceCache.workspaces.set(cwd, snapshot);
|
|
45
|
+
}
|
|
46
|
+
return snapshot;
|
|
47
|
+
}
|
|
48
|
+
function findNearestPackageDirectory(filename) {
|
|
49
|
+
let directory = path.dirname(filename);
|
|
50
|
+
const root = path.parse(directory).root;
|
|
51
|
+
while (directory !== root) {
|
|
52
|
+
if (existsSync(path.join(directory, "package.json"))) return directory;
|
|
53
|
+
directory = path.dirname(directory);
|
|
54
|
+
}
|
|
55
|
+
return path.dirname(filename);
|
|
56
|
+
}
|
|
57
|
+
function resolveSearchDirectory(context, filename) {
|
|
58
|
+
const cwd = path.resolve(context.cwd || process.cwd());
|
|
59
|
+
if (cwd !== path.parse(cwd).root && isSameOrChildPath(cwd, filename)) return cwd;
|
|
60
|
+
return findNearestPackageDirectory(filename);
|
|
61
|
+
}
|
|
62
|
+
function resolveWorkspaceDirectory(context, sourceCache, filename) {
|
|
63
|
+
const cwd = resolveSearchDirectory(context, filename);
|
|
64
|
+
const discovery = getWorkspaceSnapshot(sourceCache, cwd);
|
|
65
|
+
let closestDirectory;
|
|
66
|
+
for (const directory of discovery.directories) if (isSameOrChildPath(directory, filename) && (!closestDirectory || directory.length > closestDirectory.length)) closestDirectory = directory;
|
|
67
|
+
return {
|
|
68
|
+
workspaceDir: closestDirectory || cwd,
|
|
69
|
+
discovery
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function resolvePlan(workspaceDir, manifest) {
|
|
73
|
+
const result = loadProjectManifestSync({
|
|
74
|
+
root: workspaceDir,
|
|
75
|
+
baseManifest: manifest ?? defaultManifest
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
manifest: result.entries.length ? result.manifest : manifest,
|
|
79
|
+
dependencies: result.dependencies
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function resolveTooling(context, sourceCache, resolvedSettings) {
|
|
83
|
+
const filename = getContextFilename(context);
|
|
84
|
+
const { workspaceDir, discovery } = filename ? resolveWorkspaceDirectory(context, sourceCache, filename) : {
|
|
85
|
+
workspaceDir: context.cwd || process.cwd(),
|
|
86
|
+
discovery: void 0
|
|
87
|
+
};
|
|
88
|
+
const configuredManifestSignature = resolvedSettings.manifest ? JSON.stringify(resolvedSettings.manifest) : void 0;
|
|
89
|
+
let cache = sourceCache.cssCaches.find((cache) => cache.manifest === resolvedSettings.manifest && cache.cwd === workspaceDir);
|
|
90
|
+
if (!cache) {
|
|
91
|
+
cache = cssCaches.find((cache) => cache.manifest === resolvedSettings.manifest && cache.cwd === workspaceDir);
|
|
92
|
+
if (cache && (cache.configuredManifestSignature !== configuredManifestSignature || cache.discovery !== discovery || !cache.dependencies.every(isDependencyCurrent))) {
|
|
93
|
+
if (cache.references) {
|
|
94
|
+
cssCaches.splice(cssCaches.indexOf(cache), 1);
|
|
95
|
+
scheduleCSSCacheDisposal(cache);
|
|
96
|
+
} else disposeCSSCache(cache);
|
|
97
|
+
cache = void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!cache) {
|
|
101
|
+
const plan = filename ? resolvePlan(workspaceDir, resolvedSettings.manifest) : {
|
|
102
|
+
manifest: resolvedSettings.manifest,
|
|
103
|
+
dependencies: []
|
|
104
|
+
};
|
|
105
|
+
const resolvedManifest = plan.manifest || defaultManifest;
|
|
106
|
+
const tooling = createToolingSessionSync({ manifest: resolvedManifest });
|
|
107
|
+
cache = {
|
|
108
|
+
cwd: workspaceDir,
|
|
109
|
+
manifest: resolvedSettings.manifest,
|
|
110
|
+
tooling,
|
|
111
|
+
configuredManifestSignature,
|
|
112
|
+
dependencies: plan.dependencies.map(stampDependency),
|
|
113
|
+
discovery,
|
|
114
|
+
references: 0
|
|
115
|
+
};
|
|
116
|
+
cssCaches.push(cache);
|
|
117
|
+
}
|
|
118
|
+
if (!sourceCache.cssCaches.includes(cache)) {
|
|
119
|
+
retainCSSCache(cache);
|
|
120
|
+
sourceCache.cssCaches.push(cache);
|
|
121
|
+
}
|
|
122
|
+
return cache.tooling;
|
|
123
|
+
}
|
|
124
|
+
function resolveContext(context) {
|
|
125
|
+
const sourceCode = context.sourceCode;
|
|
126
|
+
let sourceCache = sourceCaches.get(sourceCode);
|
|
127
|
+
if (!sourceCache) {
|
|
128
|
+
sourceCache = {
|
|
129
|
+
cssCaches: [],
|
|
130
|
+
references: 0,
|
|
131
|
+
workspaces: /* @__PURE__ */ new Map(),
|
|
132
|
+
tooling: []
|
|
133
|
+
};
|
|
134
|
+
sourceCaches.set(sourceCode, sourceCache);
|
|
135
|
+
}
|
|
136
|
+
sourceCache.references++;
|
|
137
|
+
const resolvedSettings = Object.assign({}, settings, context.settings?.["@master/css"]);
|
|
138
|
+
const cwd = context.cwd || process.cwd();
|
|
139
|
+
const filename = getContextFilename(context);
|
|
140
|
+
let facade = sourceCache.tooling.find((item) => item.cwd === cwd && item.filename === filename && item.manifest === resolvedSettings.manifest);
|
|
141
|
+
if (!facade) {
|
|
142
|
+
let session;
|
|
143
|
+
const source = sourceCache;
|
|
144
|
+
facade = {
|
|
145
|
+
cwd,
|
|
146
|
+
filename,
|
|
147
|
+
manifest: resolvedSettings.manifest,
|
|
148
|
+
...createSourceTooling(() => {
|
|
149
|
+
if (!source.references) throw new Error("The ESLint source context has been released.");
|
|
150
|
+
return session ??= resolveTooling(context, source, resolvedSettings);
|
|
151
|
+
})
|
|
152
|
+
};
|
|
153
|
+
sourceCache.tooling.push(facade);
|
|
154
|
+
}
|
|
155
|
+
let released = false;
|
|
156
|
+
return {
|
|
157
|
+
settings: resolvedSettings,
|
|
158
|
+
options: context.options[0] || {},
|
|
159
|
+
tooling: facade.session,
|
|
160
|
+
release() {
|
|
161
|
+
if (released) return;
|
|
162
|
+
released = true;
|
|
163
|
+
if (--sourceCache.references !== 0) return;
|
|
164
|
+
for (const cache of sourceCache.cssCaches) {
|
|
165
|
+
cache.references--;
|
|
166
|
+
scheduleCSSCacheDisposal(cache);
|
|
167
|
+
}
|
|
168
|
+
for (const snapshot of sourceCache.workspaces.values()) releaseWorkspaceSnapshot(snapshot);
|
|
169
|
+
for (const facade of sourceCache.tooling) facade.clear();
|
|
170
|
+
sourceCache.cssCaches.length = 0;
|
|
171
|
+
sourceCache.workspaces.clear();
|
|
172
|
+
sourceCache.tooling.length = 0;
|
|
173
|
+
sourceCaches.delete(sourceCode);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export { resolveContext as default };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MasterCSSToolingSession } from "@master/css-tooling";
|
|
2
|
+
/** One facade per SourceCode/settings pair, never shared across autofix passes. */
|
|
3
|
+
declare function createSourceTooling(getSession: () => MasterCSSToolingSession): {
|
|
4
|
+
session: MasterCSSToolingSession;
|
|
5
|
+
clear(): void;
|
|
6
|
+
};
|
|
7
|
+
export { createSourceTooling };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/utils/source-tooling.ts
|
|
2
|
+
const memoizedMethods = /* @__PURE__ */ new Set([
|
|
3
|
+
"analyzeLintDocument",
|
|
4
|
+
"analyzeLintClassList",
|
|
5
|
+
"tokenizeClassList",
|
|
6
|
+
"decodeHTMLAttribute"
|
|
7
|
+
]);
|
|
8
|
+
/** One facade per SourceCode/settings pair, never shared across autofix passes. */
|
|
9
|
+
function createSourceTooling(getSession) {
|
|
10
|
+
const methods = /* @__PURE__ */ new Map();
|
|
11
|
+
const memoizedResults = [];
|
|
12
|
+
return {
|
|
13
|
+
session: new Proxy({}, { get(_, property) {
|
|
14
|
+
if (!memoizedMethods.has(property)) {
|
|
15
|
+
const session = getSession();
|
|
16
|
+
const value = Reflect.get(session, property, session);
|
|
17
|
+
return typeof value === "function" ? value.bind(session) : value;
|
|
18
|
+
}
|
|
19
|
+
let method = methods.get(property);
|
|
20
|
+
if (!method) {
|
|
21
|
+
const results = /* @__PURE__ */ new Map();
|
|
22
|
+
memoizedResults.push(results);
|
|
23
|
+
method = (...args) => {
|
|
24
|
+
const session = getSession();
|
|
25
|
+
const key = JSON.stringify(args);
|
|
26
|
+
if (!results.has(key)) results.set(key, Reflect.get(session, property).apply(session, args));
|
|
27
|
+
return results.get(key);
|
|
28
|
+
};
|
|
29
|
+
methods.set(property, method);
|
|
30
|
+
}
|
|
31
|
+
return method;
|
|
32
|
+
} }),
|
|
33
|
+
clear() {
|
|
34
|
+
for (const results of memoizedResults) results.clear();
|
|
35
|
+
memoizedResults.length = 0;
|
|
36
|
+
methods.clear();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export { createSourceTooling };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MasterCSSToolingSession } from "@master/css-tooling";
|
|
2
|
+
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
|
|
3
|
+
declare function resolveVueLiteral(node: any, sourceCode: RuleContext<any, any[]>['sourceCode'], tooling: Pick<MasterCSSToolingSession, 'decodeHTMLAttribute'>): {
|
|
4
|
+
raw: string;
|
|
5
|
+
sourceRange(a: number, b: number): [number, number];
|
|
6
|
+
encode(text: string): string;
|
|
7
|
+
};
|
|
8
|
+
export { resolveVueLiteral };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/utils/vue-literal.ts
|
|
2
|
+
const attributes = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
function boundary(mapping, offset, from, end) {
|
|
4
|
+
if (offset === 0) return 0;
|
|
5
|
+
const to = from === "range" ? "sourceRange" : "range";
|
|
6
|
+
const last = mapping.spans.at(-1);
|
|
7
|
+
if (last && offset === last[from].end) return last[to].end;
|
|
8
|
+
let low = 0;
|
|
9
|
+
let high = mapping.spans.length;
|
|
10
|
+
while (low < high) {
|
|
11
|
+
const middle = low + high >>> 1;
|
|
12
|
+
const limit = mapping.spans[middle][from].end;
|
|
13
|
+
if (limit < offset || !end && limit === offset) low = middle + 1;
|
|
14
|
+
else high = middle;
|
|
15
|
+
}
|
|
16
|
+
const span = mapping.spans[low];
|
|
17
|
+
if (!span || offset < span[from].start) return;
|
|
18
|
+
return span?.[to][end ? "end" : "start"];
|
|
19
|
+
}
|
|
20
|
+
function resolveVueLiteral(node, sourceCode, tooling) {
|
|
21
|
+
let container = node.parent;
|
|
22
|
+
while (container && container.type !== "VExpressionContainer") container = container.parent;
|
|
23
|
+
if (container?.parent?.type !== "VAttribute") return;
|
|
24
|
+
const [attributeStart, attributeEnd] = container.range;
|
|
25
|
+
const attribute = sourceCode.text.slice(attributeStart, attributeEnd);
|
|
26
|
+
const quote = attribute[0] === "\"" || attribute[0] === "'" ? attribute[0] : "";
|
|
27
|
+
const start = attributeStart + (quote ? 1 : 0);
|
|
28
|
+
const end = attributeEnd - (quote ? 1 : 0);
|
|
29
|
+
const text = sourceCode.text.slice(start, end);
|
|
30
|
+
let cached = attributes.get(container);
|
|
31
|
+
if (!cached || cached.text !== text) {
|
|
32
|
+
cached = {
|
|
33
|
+
text,
|
|
34
|
+
mapping: tooling.decodeHTMLAttribute(text)
|
|
35
|
+
};
|
|
36
|
+
attributes.set(container, cached);
|
|
37
|
+
}
|
|
38
|
+
const { mapping } = cached;
|
|
39
|
+
const from = boundary(mapping, node.range[0] - start, "sourceRange", false);
|
|
40
|
+
const to = boundary(mapping, node.range[1] - start, "sourceRange", true);
|
|
41
|
+
if (from === void 0 || to === void 0) return;
|
|
42
|
+
const raw = mapping.value.slice(from, to);
|
|
43
|
+
if (raw !== (node.type === "TemplateElement" ? "`" + node.value.raw + "`" : node.raw)) return;
|
|
44
|
+
return {
|
|
45
|
+
raw,
|
|
46
|
+
sourceRange(a, b) {
|
|
47
|
+
const first = boundary(mapping, from + a, "range", false);
|
|
48
|
+
const last = boundary(mapping, from + b, "range", true);
|
|
49
|
+
if (first === void 0 || last === void 0) throw new RangeError("Vue literal range is outside its HTML attribute.");
|
|
50
|
+
return [start + first, start + (a === b ? first : last)];
|
|
51
|
+
},
|
|
52
|
+
encode(text) {
|
|
53
|
+
return text.replace(quote ? /[&'"]/g : /[&'"<>=`\t\n\f\r ]/g, (character) => {
|
|
54
|
+
if (character === "&") return "&";
|
|
55
|
+
if (!quote || character === quote) return `&#${character.charCodeAt(0)};`;
|
|
56
|
+
return character;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export { resolveVueLiteral };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/utils/with-context-release.ts
|
|
2
|
+
function withContextRelease(visitors, release) {
|
|
3
|
+
const visitProgramExit = visitors["Program:exit"];
|
|
4
|
+
return {
|
|
5
|
+
...visitors,
|
|
6
|
+
"Program:exit"(node) {
|
|
7
|
+
try {
|
|
8
|
+
if (typeof visitProgramExit === "function") visitProgramExit(node);
|
|
9
|
+
} finally {
|
|
10
|
+
release();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export { withContextRelease as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TSESTree } from "@typescript-eslint/utils";
|
|
2
|
+
import { MasterCSSToolingSession } from "@master/css-tooling";
|
|
3
|
+
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
|
|
4
|
+
import resolveClassNode from "./resolve-class-node.js";
|
|
5
|
+
declare function withVisitClassNode(visit: (node: TSESTree.Node, resolved: ReturnType<typeof resolveClassNode>) => void, context: RuleContext<any, any[]>, lintSession: Pick<MasterCSSToolingSession, 'tokenizeClassList' | 'decodeHTMLAttribute'>): (node: any) => void;
|
|
6
|
+
export { withVisitClassNode as default };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import resolveClassNode from "./resolve-class-node.js";
|
|
2
|
+
// src/utils/with-visit-class-node.ts
|
|
3
|
+
function withVisitClassNode(visit, context, lintSession) {
|
|
4
|
+
const visitNode = (node) => {
|
|
5
|
+
if (!node) return;
|
|
6
|
+
switch (node.type) {
|
|
7
|
+
case "BinaryExpression":
|
|
8
|
+
case "Identifier": return;
|
|
9
|
+
case "TemplateLiteral":
|
|
10
|
+
node.expressions?.forEach(visitNode);
|
|
11
|
+
node.quasis.forEach(visitNode);
|
|
12
|
+
return;
|
|
13
|
+
case "JSXExpressionContainer":
|
|
14
|
+
visitNode(node.expression);
|
|
15
|
+
return;
|
|
16
|
+
case "ConditionalExpression":
|
|
17
|
+
visitNode(node.consequent);
|
|
18
|
+
visitNode(node.alternate);
|
|
19
|
+
return;
|
|
20
|
+
case "ArrayExpression":
|
|
21
|
+
node.elements.forEach((el) => {
|
|
22
|
+
visitNode(el);
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
case "LogicalExpression":
|
|
26
|
+
visitNode(node.right);
|
|
27
|
+
return;
|
|
28
|
+
case "ObjectExpression":
|
|
29
|
+
node.properties.forEach((prop) => {
|
|
30
|
+
visitNode(prop);
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
case "Property":
|
|
34
|
+
if (node.shorthand) return;
|
|
35
|
+
if (node.value && [
|
|
36
|
+
"Literal",
|
|
37
|
+
"ArrayExpression",
|
|
38
|
+
"ObjectExpression"
|
|
39
|
+
].includes(node.value.type) && typeof node.value?.value !== "boolean") visitNode(node.value);
|
|
40
|
+
else if (node.key.type === "Literal") visitNode(node.key);
|
|
41
|
+
return;
|
|
42
|
+
default:
|
|
43
|
+
const resolved = resolveClassNode(node, context, lintSession);
|
|
44
|
+
if (!resolved) return;
|
|
45
|
+
visit(node, resolved);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return visitNode;
|
|
49
|
+
}
|
|
50
|
+
export { withVisitClassNode as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DependencyStamp } from "./dependency-stamp.js";
|
|
2
|
+
interface WorkspaceSnapshot {
|
|
3
|
+
cwd: string;
|
|
4
|
+
directories: readonly string[];
|
|
5
|
+
dependencies: readonly DependencyStamp[];
|
|
6
|
+
references: number;
|
|
7
|
+
disposeTimer?: ReturnType<typeof setTimeout>;
|
|
8
|
+
}
|
|
9
|
+
declare function acquireWorkspaceSnapshot(cwd: string): WorkspaceSnapshot;
|
|
10
|
+
declare function releaseWorkspaceSnapshot(snapshot: WorkspaceSnapshot): void;
|
|
11
|
+
export { WorkspaceSnapshot, acquireWorkspaceSnapshot, releaseWorkspaceSnapshot };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isDependencyCurrent, stampDependency } from "./dependency-stamp.js";
|
|
2
|
+
import { discoverBuildWorkspaceDirectoriesSync } from "../internal/src/workspace-directories.js";
|
|
3
|
+
import { discoverManifestEntriesSync } from "@master/css-compiler/project/sync";
|
|
4
|
+
// src/utils/workspace-cache.ts
|
|
5
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
6
|
+
function acquireWorkspaceSnapshot(cwd) {
|
|
7
|
+
let snapshot = snapshots.get(cwd);
|
|
8
|
+
if (!snapshot || !snapshot.dependencies.every(isDependencyCurrent)) {
|
|
9
|
+
const dependencies = [];
|
|
10
|
+
snapshot = {
|
|
11
|
+
cwd,
|
|
12
|
+
directories: discoverBuildWorkspaceDirectoriesSync(cwd, discoverManifestEntriesSync({ root: cwd }), (file) => dependencies.push(stampDependency(file))),
|
|
13
|
+
dependencies,
|
|
14
|
+
references: 0
|
|
15
|
+
};
|
|
16
|
+
snapshots.set(cwd, snapshot);
|
|
17
|
+
}
|
|
18
|
+
if (snapshot.disposeTimer) clearTimeout(snapshot.disposeTimer);
|
|
19
|
+
snapshot.disposeTimer = void 0;
|
|
20
|
+
snapshot.references++;
|
|
21
|
+
return snapshot;
|
|
22
|
+
}
|
|
23
|
+
function releaseWorkspaceSnapshot(snapshot) {
|
|
24
|
+
if (--snapshot.references || snapshot.disposeTimer) return;
|
|
25
|
+
snapshot.disposeTimer = setTimeout(() => {
|
|
26
|
+
if (snapshots.get(snapshot.cwd) === snapshot) snapshots.delete(snapshot.cwd);
|
|
27
|
+
}, 1e3);
|
|
28
|
+
snapshot.disposeTimer.unref?.();
|
|
29
|
+
}
|
|
30
|
+
export { acquireWorkspaceSnapshot, releaseWorkspaceSnapshot };
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@master/eslint-plugin-css",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": "^24"
|
|
6
|
+
},
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mastercss",
|
|
9
|
+
"eslint",
|
|
10
|
+
"eslintplugin"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"description": "Master CSS ESLint Plugin",
|
|
14
|
+
"author": "Aoyue Design LLC.",
|
|
15
|
+
"homepage": "https://css.master.co",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/master-co/css/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/master-co/css.git",
|
|
22
|
+
"directory": "packages/eslint-plugin"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@master/css": "0.0.0-site.1793659883.g69c9aebcf628",
|
|
42
|
+
"@master/css-compiler": "0.0.0-site.1793659883.g69c9aebcf628",
|
|
43
|
+
"@master/css-tooling": "0.0.0-site.1793659883.g69c9aebcf628",
|
|
44
|
+
"@master/css-preset": "0.0.0-site.1793659883.g69c9aebcf628",
|
|
45
|
+
"@typescript-eslint/utils": "^8.72.0",
|
|
46
|
+
"typescript-eslint": "^8.72.0"
|
|
47
|
+
},
|
|
48
|
+
"version": "0.0.0-site.1793659883.g69c9aebcf628",
|
|
49
|
+
"gitHead": "69c9aebcf628eb529001b9fa1b3536306f1b139e"
|
|
50
|
+
}
|