@rafters/veneer 0.0.1 → 0.0.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/bin.d.mts +1 -0
- package/dist/bin.mjs +54 -0
- package/dist/check-CbVCXbX6.mjs +157 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +2 -155
- package/dist/{lint-BabXVYQJ.mjs → lint-Tvq5uHHS.mjs} +126 -37
- package/dist/worker.mjs +3 -2
- package/package.json +4 -1
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as relativeFile } from "./lint-Tvq5uHHS.mjs";
|
|
3
|
+
import { t as check } from "./check-CbVCXbX6.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { text } from "node:stream/consumers";
|
|
8
|
+
//#region src/bin.ts
|
|
9
|
+
const COULD_NOT_RUN = 2;
|
|
10
|
+
function parsedArguments() {
|
|
11
|
+
try {
|
|
12
|
+
const { values, positionals } = parseArgs({
|
|
13
|
+
allowPositionals: true,
|
|
14
|
+
options: { stdin: { type: "string" } }
|
|
15
|
+
});
|
|
16
|
+
const [root, ...rest] = positionals;
|
|
17
|
+
return rest.length > 0 ? void 0 : {
|
|
18
|
+
root,
|
|
19
|
+
stdinPath: values.stdin
|
|
20
|
+
};
|
|
21
|
+
} catch {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function resolvedLinks(file) {
|
|
26
|
+
if (existsSync(file)) return realpathSync(file);
|
|
27
|
+
const parent = path.dirname(file);
|
|
28
|
+
return parent === file ? file : path.join(resolvedLinks(parent), path.basename(file));
|
|
29
|
+
}
|
|
30
|
+
async function run(root, stdinPath) {
|
|
31
|
+
const file = stdinPath && relativeFile(resolvedLinks(root), resolvedLinks(path.resolve(stdinPath)));
|
|
32
|
+
const unsaved = file === void 0 ? void 0 : { [file]: await text(process.stdin) };
|
|
33
|
+
const result = await check(root, { unsaved });
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
for (const { message } of result.errors) process.stderr.write(`${message}\n`);
|
|
36
|
+
return COULD_NOT_RUN;
|
|
37
|
+
}
|
|
38
|
+
process.stdout.write(result.report);
|
|
39
|
+
for (const { message } of result.notices) process.stderr.write(`${message}\n`);
|
|
40
|
+
return result.diagnostics.some(({ severity }) => severity === "error") ? 1 : 0;
|
|
41
|
+
}
|
|
42
|
+
const parsed = parsedArguments();
|
|
43
|
+
if (parsed) try {
|
|
44
|
+
process.exitCode = await run(parsed.root ?? process.cwd(), parsed.stdinPath);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
47
|
+
process.exitCode = COULD_NOT_RUN;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
process.stderr.write("usage: rafters-check [root] [--stdin <path>]\n");
|
|
51
|
+
process.exitCode = COULD_NOT_RUN;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { a as resolveComponents, c as readConfig, n as report, o as ProjectError, r as compareCodePoints, s as openProject, t as lint } from "./lint-Tvq5uHHS.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { lstatSync } from "node:fs";
|
|
5
|
+
//#region src/docs.ts
|
|
6
|
+
function propLine(prop) {
|
|
7
|
+
return {
|
|
8
|
+
name: prop.name,
|
|
9
|
+
type: prop.type,
|
|
10
|
+
optional: prop.optional,
|
|
11
|
+
...prop.default !== void 0 && { default: prop.default },
|
|
12
|
+
...prop.values !== void 0 && { values: prop.values },
|
|
13
|
+
...prop.doc !== void 0 && { doc: prop.doc },
|
|
14
|
+
file: prop.file,
|
|
15
|
+
line: prop.line,
|
|
16
|
+
column: prop.column
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function componentLine(component) {
|
|
20
|
+
return {
|
|
21
|
+
kind: "component",
|
|
22
|
+
name: component.name,
|
|
23
|
+
file: component.file,
|
|
24
|
+
resolved: component.resolved,
|
|
25
|
+
...component.reason !== void 0 && { reason: component.reason },
|
|
26
|
+
...component.description !== void 0 && { description: component.description },
|
|
27
|
+
tags: component.tags,
|
|
28
|
+
props: component.props.map(propLine),
|
|
29
|
+
inherited: component.inherited,
|
|
30
|
+
inheritedDefaults: component.inheritedDefaults
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function docsText(intent, components) {
|
|
34
|
+
return [{
|
|
35
|
+
kind: "project",
|
|
36
|
+
intent
|
|
37
|
+
}, ...components.map(componentLine)].map((line) => `${JSON.stringify(line)}\n`).join("");
|
|
38
|
+
}
|
|
39
|
+
async function writeWhole(docsPath, text) {
|
|
40
|
+
const temporary = `${docsPath}.tmp`;
|
|
41
|
+
await mkdir(path.dirname(docsPath), { recursive: true });
|
|
42
|
+
try {
|
|
43
|
+
await rm(temporary, { force: true });
|
|
44
|
+
await writeFile(temporary, text, { flag: "wx" });
|
|
45
|
+
await rename(temporary, docsPath);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await rm(temporary, { force: true });
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function openOrError(root, unsaved) {
|
|
52
|
+
try {
|
|
53
|
+
return await openProject(root, { unsaved });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof ProjectError && error.code === "tsconfig-missing") return {
|
|
56
|
+
code: error.code,
|
|
57
|
+
message: error.message
|
|
58
|
+
};
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function outputSymlinkError(root) {
|
|
63
|
+
const symlink = [".rafters", ".rafters/veneer"].find((folder) => lstatSync(path.join(root, folder), { throwIfNoEntry: false })?.isSymbolicLink());
|
|
64
|
+
if (!symlink) return void 0;
|
|
65
|
+
return {
|
|
66
|
+
code: "output-symlink",
|
|
67
|
+
message: `${symlink} is a symbolic link; veneer writes only inside the project`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function sortedNotices(notices) {
|
|
71
|
+
return notices.toSorted((a, b) => compareCodePoints(a.code, b.code) || compareCodePoints(a.message, b.message));
|
|
72
|
+
}
|
|
73
|
+
async function writeDocsFrom(project, config) {
|
|
74
|
+
const { components, notices } = await resolveComponents(project, config.componentFolders);
|
|
75
|
+
const docsPath = path.join(project.root, ".rafters/veneer/docs.jsonl");
|
|
76
|
+
await writeWhole(docsPath, docsText(config.intent, components));
|
|
77
|
+
return {
|
|
78
|
+
docsPath,
|
|
79
|
+
notices: sortedNotices([...config.notices, ...notices])
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function writeDocs(projectRoot) {
|
|
83
|
+
const root = path.resolve(projectRoot);
|
|
84
|
+
const config = await readConfig(root);
|
|
85
|
+
if (!config.ok) return {
|
|
86
|
+
ok: false,
|
|
87
|
+
errors: config.errors
|
|
88
|
+
};
|
|
89
|
+
const symlink = outputSymlinkError(root);
|
|
90
|
+
if (symlink) return {
|
|
91
|
+
ok: false,
|
|
92
|
+
errors: [symlink]
|
|
93
|
+
};
|
|
94
|
+
const project = await openOrError(root);
|
|
95
|
+
if ("code" in project) return {
|
|
96
|
+
ok: false,
|
|
97
|
+
errors: [project]
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
...await writeDocsFrom(project, config)
|
|
103
|
+
};
|
|
104
|
+
} finally {
|
|
105
|
+
project.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/check.ts
|
|
110
|
+
function outsideProgram(file) {
|
|
111
|
+
return {
|
|
112
|
+
code: "outside-program",
|
|
113
|
+
message: `${file} is not in the tsconfig's files, so it was not checked`
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function check(projectRoot, options = {}) {
|
|
117
|
+
const root = path.resolve(projectRoot);
|
|
118
|
+
const config = await readConfig(root);
|
|
119
|
+
if (!config.ok) return {
|
|
120
|
+
ok: false,
|
|
121
|
+
errors: config.errors
|
|
122
|
+
};
|
|
123
|
+
const unsaved = options.unsaved ?? {};
|
|
124
|
+
const unsavedFiles = Object.keys(unsaved);
|
|
125
|
+
const isSave = options.save === true && unsavedFiles.length === 0;
|
|
126
|
+
const symlink = isSave ? outputSymlinkError(root) : void 0;
|
|
127
|
+
if (symlink) return {
|
|
128
|
+
ok: false,
|
|
129
|
+
errors: [symlink]
|
|
130
|
+
};
|
|
131
|
+
const project = await openOrError(root, unsaved);
|
|
132
|
+
if ("code" in project) return {
|
|
133
|
+
ok: false,
|
|
134
|
+
errors: [project]
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const roots = project.checker.getRootFileNames();
|
|
138
|
+
const outside = unsavedFiles.filter((file) => !roots.includes(path.join(root, file)));
|
|
139
|
+
const asked = options.paths ?? (unsavedFiles.length > 0 ? unsavedFiles : void 0);
|
|
140
|
+
const diagnostics = await lint(project, {
|
|
141
|
+
componentFolders: config.componentFolders,
|
|
142
|
+
scan: config.scan,
|
|
143
|
+
files: isSave ? void 0 : asked?.filter((file) => !outside.includes(file))
|
|
144
|
+
});
|
|
145
|
+
const written = isSave ? await writeDocsFrom(project, config) : void 0;
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
diagnostics,
|
|
149
|
+
report: report(diagnostics),
|
|
150
|
+
...written ?? { notices: sortedNotices([...config.notices, ...outside.map(outsideProgram)]) }
|
|
151
|
+
};
|
|
152
|
+
} finally {
|
|
153
|
+
project.close();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { writeDocs as n, check as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AstroCheck, Diagnostic as Diagnostic$1 } from "@astrojs/language-server";
|
|
2
2
|
import * as TypeScript from "typescript";
|
|
3
3
|
//#region src/config.d.ts
|
|
4
|
+
declare const SCANS: readonly ["system", "format", "all"];
|
|
5
|
+
type Scan = (typeof SCANS)[number];
|
|
4
6
|
interface Notice {
|
|
5
7
|
code: string;
|
|
6
8
|
message: string;
|
|
@@ -47,6 +49,7 @@ interface LintDiagnostic extends Diagnostic {
|
|
|
47
49
|
}
|
|
48
50
|
export declare function lint(project: Project, options: {
|
|
49
51
|
componentFolders: string[];
|
|
52
|
+
scan?: Scan;
|
|
50
53
|
files?: string[];
|
|
51
54
|
}): Promise<LintDiagnostic[]>;
|
|
52
55
|
export declare function report(diagnostics: LintDiagnostic[]): string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,156 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
-
import { lstatSync } from "node:fs";
|
|
5
|
-
//#region src/docs.ts
|
|
6
|
-
function propLine(prop) {
|
|
7
|
-
return {
|
|
8
|
-
name: prop.name,
|
|
9
|
-
type: prop.type,
|
|
10
|
-
optional: prop.optional,
|
|
11
|
-
...prop.default !== void 0 && { default: prop.default },
|
|
12
|
-
...prop.values !== void 0 && { values: prop.values },
|
|
13
|
-
...prop.doc !== void 0 && { doc: prop.doc },
|
|
14
|
-
file: prop.file,
|
|
15
|
-
line: prop.line,
|
|
16
|
-
column: prop.column
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function componentLine(component) {
|
|
20
|
-
return {
|
|
21
|
-
kind: "component",
|
|
22
|
-
name: component.name,
|
|
23
|
-
file: component.file,
|
|
24
|
-
resolved: component.resolved,
|
|
25
|
-
...component.reason !== void 0 && { reason: component.reason },
|
|
26
|
-
...component.description !== void 0 && { description: component.description },
|
|
27
|
-
tags: component.tags,
|
|
28
|
-
props: component.props.map(propLine),
|
|
29
|
-
inherited: component.inherited,
|
|
30
|
-
inheritedDefaults: component.inheritedDefaults
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function docsText(intent, components) {
|
|
34
|
-
return [{
|
|
35
|
-
kind: "project",
|
|
36
|
-
intent
|
|
37
|
-
}, ...components.map(componentLine)].map((line) => `${JSON.stringify(line)}\n`).join("");
|
|
38
|
-
}
|
|
39
|
-
async function writeWhole(docsPath, text) {
|
|
40
|
-
const temporary = `${docsPath}.tmp`;
|
|
41
|
-
await mkdir(path.dirname(docsPath), { recursive: true });
|
|
42
|
-
try {
|
|
43
|
-
await rm(temporary, { force: true });
|
|
44
|
-
await writeFile(temporary, text, { flag: "wx" });
|
|
45
|
-
await rename(temporary, docsPath);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
await rm(temporary, { force: true });
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async function openOrError(root, unsaved) {
|
|
52
|
-
try {
|
|
53
|
-
return await openProject(root, { unsaved });
|
|
54
|
-
} catch (error) {
|
|
55
|
-
if (error instanceof ProjectError && error.code === "tsconfig-missing") return {
|
|
56
|
-
code: error.code,
|
|
57
|
-
message: error.message
|
|
58
|
-
};
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function outputSymlinkError(root) {
|
|
63
|
-
const symlink = [".rafters", ".rafters/veneer"].find((folder) => lstatSync(path.join(root, folder), { throwIfNoEntry: false })?.isSymbolicLink());
|
|
64
|
-
if (!symlink) return void 0;
|
|
65
|
-
return {
|
|
66
|
-
code: "output-symlink",
|
|
67
|
-
message: `${symlink} is a symbolic link; veneer writes only inside the project`
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function sortedNotices(notices) {
|
|
71
|
-
return notices.toSorted((a, b) => compareCodePoints(a.code, b.code) || compareCodePoints(a.message, b.message));
|
|
72
|
-
}
|
|
73
|
-
async function writeDocsFrom(project, config) {
|
|
74
|
-
const { components, notices } = await resolveComponents(project, config.componentFolders);
|
|
75
|
-
const docsPath = path.join(project.root, ".rafters/veneer/docs.jsonl");
|
|
76
|
-
await writeWhole(docsPath, docsText(config.intent, components));
|
|
77
|
-
return {
|
|
78
|
-
docsPath,
|
|
79
|
-
notices: sortedNotices([...config.notices, ...notices])
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
async function writeDocs(projectRoot) {
|
|
83
|
-
const root = path.resolve(projectRoot);
|
|
84
|
-
const config = await readConfig(root);
|
|
85
|
-
if (!config.ok) return {
|
|
86
|
-
ok: false,
|
|
87
|
-
errors: config.errors
|
|
88
|
-
};
|
|
89
|
-
const symlink = outputSymlinkError(root);
|
|
90
|
-
if (symlink) return {
|
|
91
|
-
ok: false,
|
|
92
|
-
errors: [symlink]
|
|
93
|
-
};
|
|
94
|
-
const project = await openOrError(root);
|
|
95
|
-
if ("code" in project) return {
|
|
96
|
-
ok: false,
|
|
97
|
-
errors: [project]
|
|
98
|
-
};
|
|
99
|
-
try {
|
|
100
|
-
return {
|
|
101
|
-
ok: true,
|
|
102
|
-
...await writeDocsFrom(project, config)
|
|
103
|
-
};
|
|
104
|
-
} finally {
|
|
105
|
-
project.close();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/check.ts
|
|
110
|
-
function outsideProgram(file) {
|
|
111
|
-
return {
|
|
112
|
-
code: "outside-program",
|
|
113
|
-
message: `${file} is not in the tsconfig's files, so it was not checked`
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
async function check(projectRoot, options = {}) {
|
|
117
|
-
const root = path.resolve(projectRoot);
|
|
118
|
-
const config = await readConfig(root);
|
|
119
|
-
if (!config.ok) return {
|
|
120
|
-
ok: false,
|
|
121
|
-
errors: config.errors
|
|
122
|
-
};
|
|
123
|
-
const unsaved = options.unsaved ?? {};
|
|
124
|
-
const unsavedFiles = Object.keys(unsaved);
|
|
125
|
-
const isSave = options.save === true && unsavedFiles.length === 0;
|
|
126
|
-
const symlink = isSave ? outputSymlinkError(root) : void 0;
|
|
127
|
-
if (symlink) return {
|
|
128
|
-
ok: false,
|
|
129
|
-
errors: [symlink]
|
|
130
|
-
};
|
|
131
|
-
const project = await openOrError(root, unsaved);
|
|
132
|
-
if ("code" in project) return {
|
|
133
|
-
ok: false,
|
|
134
|
-
errors: [project]
|
|
135
|
-
};
|
|
136
|
-
try {
|
|
137
|
-
const roots = project.checker.getRootFileNames();
|
|
138
|
-
const outside = unsavedFiles.filter((file) => !roots.includes(path.join(root, file)));
|
|
139
|
-
const asked = options.paths ?? (unsavedFiles.length > 0 ? unsavedFiles : void 0);
|
|
140
|
-
const diagnostics = await lint(project, {
|
|
141
|
-
componentFolders: config.componentFolders,
|
|
142
|
-
files: isSave ? void 0 : asked?.filter((file) => !outside.includes(file))
|
|
143
|
-
});
|
|
144
|
-
const written = isSave ? await writeDocsFrom(project, config) : void 0;
|
|
145
|
-
return {
|
|
146
|
-
ok: true,
|
|
147
|
-
diagnostics,
|
|
148
|
-
report: report(diagnostics),
|
|
149
|
-
...written ?? { notices: sortedNotices([...config.notices, ...outside.map(outsideProgram)]) }
|
|
150
|
-
};
|
|
151
|
-
} finally {
|
|
152
|
-
project.close();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
//#endregion
|
|
1
|
+
import { n as report, o as ProjectError, s as openProject, t as lint } from "./lint-Tvq5uHHS.mjs";
|
|
2
|
+
import { n as writeDocs, t as check } from "./check-CbVCXbX6.mjs";
|
|
156
3
|
export { ProjectError, check, lint, openProject, report, writeDocs };
|
|
@@ -6,6 +6,12 @@ import { format } from "node:util";
|
|
|
6
6
|
import { AstroCheck } from "@astrojs/language-server";
|
|
7
7
|
//#region src/config.ts
|
|
8
8
|
const CONFIG_FILE = ".rafters/config.rafters.json";
|
|
9
|
+
const SCANS = [
|
|
10
|
+
"system",
|
|
11
|
+
"format",
|
|
12
|
+
"all"
|
|
13
|
+
];
|
|
14
|
+
const isScan = (value) => SCANS.some((scan) => scan === value);
|
|
9
15
|
function isObject(value) {
|
|
10
16
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
17
|
}
|
|
@@ -98,7 +104,10 @@ async function readConfig(root) {
|
|
|
98
104
|
}
|
|
99
105
|
if (intent !== void 0 && typeof intent !== "string") errors.push(fieldError("intent", "expected a string", intent));
|
|
100
106
|
if (veneer !== void 0 && !isObject(veneer)) errors.push(fieldError("veneer", "expected an object", veneer));
|
|
101
|
-
else
|
|
107
|
+
else {
|
|
108
|
+
if (veneer?.mdxPath !== void 0 && typeof veneer.mdxPath !== "string") errors.push(fieldError("veneer.mdxPath", "expected a string", veneer.mdxPath));
|
|
109
|
+
if (veneer?.scan !== void 0 && !isScan(veneer.scan)) errors.push(fieldError("veneer.scan", `expected one of ${SCANS.join(", ")}`, veneer.scan));
|
|
110
|
+
}
|
|
102
111
|
if (errors.length > 0) return {
|
|
103
112
|
ok: false,
|
|
104
113
|
errors
|
|
@@ -107,6 +116,7 @@ async function readConfig(root) {
|
|
|
107
116
|
ok: true,
|
|
108
117
|
intent: typeof intent === "string" ? intent : null,
|
|
109
118
|
componentFolders,
|
|
119
|
+
scan: isObject(veneer) && isScan(veneer.scan) ? veneer.scan : "format",
|
|
110
120
|
notices
|
|
111
121
|
};
|
|
112
122
|
}
|
|
@@ -669,7 +679,15 @@ function ownPropNames(reader, component, sourceFile) {
|
|
|
669
679
|
if (!signature) return [];
|
|
670
680
|
return propsOfSignature(reader, signature, sourceFile, /* @__PURE__ */ new Map()).props.map(({ name }) => name);
|
|
671
681
|
}
|
|
672
|
-
|
|
682
|
+
const INTELLIGENCE_TAGS = [
|
|
683
|
+
"cognitive-load",
|
|
684
|
+
"attention-economics",
|
|
685
|
+
"trust-building",
|
|
686
|
+
"accessibility",
|
|
687
|
+
"semantic-meaning",
|
|
688
|
+
"usage-patterns"
|
|
689
|
+
];
|
|
690
|
+
function placedBlocks(reader, sourceFile) {
|
|
673
691
|
const { ts } = reader;
|
|
674
692
|
const blocks = [];
|
|
675
693
|
const visit = (node) => {
|
|
@@ -684,7 +702,16 @@ function intelligenceBlock(reader, sourceFile) {
|
|
|
684
702
|
block,
|
|
685
703
|
at
|
|
686
704
|
}];
|
|
687
|
-
})
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
function intelligenceBlock(reader, sourceFile) {
|
|
708
|
+
return placedBlocks(reader, sourceFile).sort((a, b) => a.at - b.at)[0]?.block;
|
|
709
|
+
}
|
|
710
|
+
function holdsAllIntelligenceTags(reader, sourceFile) {
|
|
711
|
+
return placedBlocks(reader, sourceFile).some(({ block }) => {
|
|
712
|
+
const names = new Set(block.tags?.map((tag) => tag.tagName.text));
|
|
713
|
+
return INTELLIGENCE_TAGS.every((tag) => names.has(tag));
|
|
714
|
+
});
|
|
688
715
|
}
|
|
689
716
|
function intelligence(reader, sourceFile) {
|
|
690
717
|
const { ts } = reader;
|
|
@@ -785,6 +812,12 @@ const CLASS_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
|
785
812
|
]);
|
|
786
813
|
const MAX_RESOLVE_STEPS = 8;
|
|
787
814
|
const realPath = (file) => existsSync(file) ? realpathSync(file) : file;
|
|
815
|
+
function qualifies(reader, sourceFile) {
|
|
816
|
+
const { fileName } = sourceFile;
|
|
817
|
+
const stem = fileName.replace(/\.(tsx|astro)$/, "");
|
|
818
|
+
const { sys } = reader.ts;
|
|
819
|
+
return stem !== fileName && sys.fileExists(`${stem}.behavior.ts`) && sys.fileExists(`${stem}.classes.ts`) && holdsAllIntelligenceTags(reader, sourceFile);
|
|
820
|
+
}
|
|
788
821
|
function offsetAt(text, line, column) {
|
|
789
822
|
const breaks = /\r\n|\r|\n/g;
|
|
790
823
|
let start = 0;
|
|
@@ -817,8 +850,8 @@ function elementUsing(ts, node) {
|
|
|
817
850
|
const element = tag.parent;
|
|
818
851
|
return ts.isJsxOpeningLikeElement(element) && element.tagName === tag ? element : void 0;
|
|
819
852
|
}
|
|
820
|
-
function
|
|
821
|
-
let symbol =
|
|
853
|
+
function resolveSymbol({ ts, tc }, start) {
|
|
854
|
+
let symbol = start;
|
|
822
855
|
for (let step = 0; symbol && step < MAX_RESOLVE_STEPS; step += 1) {
|
|
823
856
|
const declaration = symbol.valueDeclaration;
|
|
824
857
|
if (symbol.flags & ts.SymbolFlags.Alias) symbol = tc.getAliasedSymbol(symbol);
|
|
@@ -826,6 +859,10 @@ function resolveTag({ ts, tc }, { tagName }) {
|
|
|
826
859
|
else return symbol;
|
|
827
860
|
}
|
|
828
861
|
}
|
|
862
|
+
function resolveTag(reader, { tagName }) {
|
|
863
|
+
const { ts, tc } = reader;
|
|
864
|
+
return resolveSymbol(reader, tc.getSymbolAtLocation(ts.isPropertyAccessExpression(tagName) ? tagName.name : tagName));
|
|
865
|
+
}
|
|
829
866
|
function deprecatedText({ ts, tc }, element, name) {
|
|
830
867
|
const parameter = tc.getResolvedSignature(element)?.parameters[0];
|
|
831
868
|
const tag = (parameter && tc.getTypeOfSymbol(parameter).getProperty(name))?.getJsDocTags(tc).find((found) => found.name === "deprecated");
|
|
@@ -842,46 +879,60 @@ function utilityOf(className) {
|
|
|
842
879
|
}
|
|
843
880
|
return className.slice(start);
|
|
844
881
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if (ts.
|
|
849
|
-
|
|
850
|
-
|
|
882
|
+
function constString(reader, identifier) {
|
|
883
|
+
const { ts, tc } = reader;
|
|
884
|
+
const declaration = resolveSymbol(reader, tc.getSymbolAtLocation(identifier))?.valueDeclaration;
|
|
885
|
+
if (!declaration || !ts.isVariableDeclaration(declaration) || !(ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) || !declaration.initializer) return;
|
|
886
|
+
return ts.isStringLiteralLike(declaration.initializer) ? declaration.initializer : void 0;
|
|
887
|
+
}
|
|
888
|
+
function collectClassStrings(reader, sourceFile, isOwnSource, node, found) {
|
|
889
|
+
const { ts } = reader;
|
|
890
|
+
if (ts.isStringLiteralLike(node)) found.set(node, node);
|
|
891
|
+
else if (ts.isIdentifier(node)) {
|
|
892
|
+
const literal = constString(reader, node);
|
|
893
|
+
const declaredIn = literal?.getSourceFile();
|
|
894
|
+
if (!literal || !declaredIn) return;
|
|
895
|
+
if (declaredIn === sourceFile) found.set(literal, literal);
|
|
896
|
+
else if (isOwnSource(declaredIn.fileName)) found.set(node, literal);
|
|
897
|
+
} else ts.forEachChild(node, (child) => {
|
|
898
|
+
collectClassStrings(reader, sourceFile, isOwnSource, child, found);
|
|
899
|
+
});
|
|
851
900
|
}
|
|
852
|
-
function bracketsInLiteral(reader, file, sourceFile, literal) {
|
|
853
|
-
return [...literal.getText(
|
|
901
|
+
function bracketsInLiteral(reader, file, sourceFile, at, literal) {
|
|
902
|
+
return [...literal.getText(literal.getSourceFile()).slice(1, -1).matchAll(/\S+/g)].flatMap((match) => {
|
|
854
903
|
const [className] = match;
|
|
855
904
|
if (!utilityOf(className).includes("[")) return [];
|
|
856
|
-
const
|
|
905
|
+
const inLiteral = at === literal;
|
|
906
|
+
const position = positionOf(reader, sourceFile, at.getStart(sourceFile) + (inLiteral ? 1 + match.index : 0));
|
|
857
907
|
if (!position) return [];
|
|
858
908
|
return [{
|
|
859
909
|
file,
|
|
860
910
|
line: position.line,
|
|
861
911
|
column: position.column,
|
|
862
912
|
endLine: position.line,
|
|
863
|
-
endColumn: position.column + className.length,
|
|
913
|
+
endColumn: position.column + (inLiteral ? className.length : at.getWidth(sourceFile)),
|
|
864
914
|
severity: "error",
|
|
865
915
|
code: "rafters(arbitrary-value)",
|
|
866
916
|
message: `${className} is an arbitrary value`
|
|
867
917
|
}];
|
|
868
918
|
});
|
|
869
919
|
}
|
|
870
|
-
function bracketValues(reader, file, sourceFile) {
|
|
920
|
+
function bracketValues(reader, file, sourceFile, isOwnSource) {
|
|
871
921
|
const { ts } = reader;
|
|
872
|
-
const
|
|
922
|
+
const strings = /* @__PURE__ */ new Map();
|
|
873
923
|
const visit = (node) => {
|
|
874
924
|
if (ts.isJsxAttribute(node) && node.initializer) {
|
|
875
925
|
const { name } = node;
|
|
876
926
|
const attribute = ts.isJsxNamespacedName(name) ? `${name.namespace.text}:${name.name.text}` : name.text;
|
|
877
|
-
if (CLASS_ATTRIBUTES.has(attribute))
|
|
878
|
-
}
|
|
927
|
+
if (CLASS_ATTRIBUTES.has(attribute)) collectClassStrings(reader, sourceFile, isOwnSource, node.initializer, strings);
|
|
928
|
+
} else if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "classy") for (const argument of node.arguments) collectClassStrings(reader, sourceFile, isOwnSource, argument, strings);
|
|
879
929
|
ts.forEachChild(node, visit);
|
|
880
930
|
};
|
|
881
931
|
visit(sourceFile);
|
|
882
|
-
return
|
|
932
|
+
return [...strings].flatMap(([at, literal]) => bracketsInLiteral(reader, file, sourceFile, at, literal));
|
|
883
933
|
}
|
|
884
934
|
const FILL_WORD = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
935
|
+
const FILL_TOKEN = /^bg-fill-[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
885
936
|
const FILL_ALPHA = /^(100|[1-9]?[0-9])$/;
|
|
886
937
|
const AUTHORED_FILL = "a fill the designer authors, fill=\"bg-fill-<name>\"";
|
|
887
938
|
const isFillStop = (stop) => {
|
|
@@ -901,17 +952,17 @@ async function loadSurfaces(root) {
|
|
|
901
952
|
return { failure: error instanceof Error ? error.message : String(error) };
|
|
902
953
|
}
|
|
903
954
|
}
|
|
904
|
-
function
|
|
955
|
+
function valueLiteral(ts, node) {
|
|
905
956
|
const { initializer } = node;
|
|
906
957
|
if (!initializer) return void 0;
|
|
907
958
|
if (ts.isStringLiteral(initializer)) return initializer;
|
|
908
959
|
const inner = ts.isJsxExpression(initializer) ? initializer.expression : void 0;
|
|
909
960
|
return inner && ts.isStringLiteralLike(inner) ? inner : void 0;
|
|
910
961
|
}
|
|
911
|
-
function
|
|
962
|
+
function valueAttributes({ ts }, sourceFile) {
|
|
912
963
|
const found = [];
|
|
913
964
|
const visit = (node) => {
|
|
914
|
-
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "fill") found.push(node);
|
|
965
|
+
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && (node.name.text === "fill" || node.name.text === "surface")) found.push(node);
|
|
915
966
|
ts.forEachChild(node, visit);
|
|
916
967
|
};
|
|
917
968
|
visit(sourceFile);
|
|
@@ -933,6 +984,17 @@ async function lint(project, options) {
|
|
|
933
984
|
const real = realPath(file);
|
|
934
985
|
return folders.some((folder) => real.startsWith(folder));
|
|
935
986
|
};
|
|
987
|
+
const scan = options.scan ?? "format";
|
|
988
|
+
const projectRoot = realPath(project.root);
|
|
989
|
+
const isOwnSource = (file) => {
|
|
990
|
+
const [first, ...rest] = path.relative(projectRoot, realPath(file)).split(path.sep);
|
|
991
|
+
return first !== ".." && first !== "node_modules" && !rest.includes("node_modules");
|
|
992
|
+
};
|
|
993
|
+
const isExempt = (sourceFile) => isUnderFolder(sourceFile.fileName) || qualifies(reader, sourceFile);
|
|
994
|
+
const isCheckedUse = (declaredIn) => {
|
|
995
|
+
if (scan === "system") return isUnderFolder(declaredIn.fileName);
|
|
996
|
+
return isExempt(declaredIn) || scan === "all" && isOwnSource(declaredIn.fileName);
|
|
997
|
+
};
|
|
936
998
|
const propsHelp = /* @__PURE__ */ new Map();
|
|
937
999
|
const reportedUse = (sourceFile, diagnostic) => {
|
|
938
1000
|
const offset = programOffset(reader, sourceFile, diagnostic);
|
|
@@ -943,7 +1005,7 @@ async function lint(project, options) {
|
|
|
943
1005
|
const declaration = component?.valueDeclaration ?? component?.declarations?.[0];
|
|
944
1006
|
if (!element || !component || !declaration) return void 0;
|
|
945
1007
|
const declaredIn = declaration.getSourceFile();
|
|
946
|
-
if (!
|
|
1008
|
+
if (!isCheckedUse(declaredIn)) return void 0;
|
|
947
1009
|
let help;
|
|
948
1010
|
if (diagnostic.code === "ts(6385)") help = deprecatedText(reader, element, node.getText(sourceFile));
|
|
949
1011
|
else {
|
|
@@ -966,15 +1028,44 @@ async function lint(project, options) {
|
|
|
966
1028
|
if ("failure" in loaded) return `the project's tokens could not be loaded, so no replacement is named: ${loaded.failure}`;
|
|
967
1029
|
return loaded.words.has(value) ? `surface="${value}"` : AUTHORED_FILL;
|
|
968
1030
|
};
|
|
969
|
-
const
|
|
1031
|
+
const fillFinding = async (value) => {
|
|
1032
|
+
if (value === "" || FILL_TOKEN.test(value)) return void 0;
|
|
1033
|
+
const form = retiredFillForm(value);
|
|
1034
|
+
if (!form) return {
|
|
1035
|
+
code: "rafters(invalid-fill)",
|
|
1036
|
+
message: `fill="${value}" is not exactly one bg-fill-* token`
|
|
1037
|
+
};
|
|
1038
|
+
return {
|
|
1039
|
+
code: "rafters(retired-fill)",
|
|
1040
|
+
message: `fill="${value}" is a retired fill value`,
|
|
1041
|
+
help: await replacement(value, form)
|
|
1042
|
+
};
|
|
1043
|
+
};
|
|
1044
|
+
const surfaceFinding = async (value) => {
|
|
1045
|
+
if (value === "") return void 0;
|
|
1046
|
+
surfaces ??= loadSurfaces(project.root);
|
|
1047
|
+
const loaded = await surfaces;
|
|
1048
|
+
if ("failure" in loaded) return {
|
|
1049
|
+
code: "rafters(unknown-surface)",
|
|
1050
|
+
message: `surface="${value}" could not be checked`,
|
|
1051
|
+
help: `the project's tokens could not be loaded, so no surface is named: ${loaded.failure}`
|
|
1052
|
+
};
|
|
1053
|
+
if (loaded.words.has(value)) return void 0;
|
|
1054
|
+
return {
|
|
1055
|
+
code: "rafters(unknown-surface)",
|
|
1056
|
+
message: `surface="${value}" is not a surface in the project's tokens`
|
|
1057
|
+
};
|
|
1058
|
+
};
|
|
1059
|
+
const badValues = async (file, sourceFile) => {
|
|
970
1060
|
const flagged = [];
|
|
971
|
-
for (const attribute of
|
|
972
|
-
const literal =
|
|
973
|
-
|
|
974
|
-
if (!literal || !form) continue;
|
|
1061
|
+
for (const attribute of valueAttributes(reader, sourceFile)) {
|
|
1062
|
+
const literal = valueLiteral(ts, attribute);
|
|
1063
|
+
if (!literal) continue;
|
|
975
1064
|
const component = resolveTag(reader, attribute.parent.parent);
|
|
976
1065
|
const declaration = component?.valueDeclaration ?? component?.declarations?.[0];
|
|
977
|
-
if (!declaration || !
|
|
1066
|
+
if (!declaration || !isCheckedUse(declaration.getSourceFile())) continue;
|
|
1067
|
+
const finding = await (attribute.name.getText(sourceFile) === "fill" ? fillFinding(literal.text) : surfaceFinding(literal.text));
|
|
1068
|
+
if (!finding) continue;
|
|
978
1069
|
const start = positionOf(reader, sourceFile, attribute.getStart(sourceFile));
|
|
979
1070
|
const end = positionOf(reader, sourceFile, attribute.end);
|
|
980
1071
|
if (!start || !end) continue;
|
|
@@ -985,9 +1076,7 @@ async function lint(project, options) {
|
|
|
985
1076
|
endLine: end.line,
|
|
986
1077
|
endColumn: end.column,
|
|
987
1078
|
severity: "error",
|
|
988
|
-
|
|
989
|
-
message: `fill="${literal.text}" is a retired fill value`,
|
|
990
|
-
help: await replacement(literal.text, form)
|
|
1079
|
+
...finding
|
|
991
1080
|
});
|
|
992
1081
|
}
|
|
993
1082
|
return flagged;
|
|
@@ -1001,9 +1090,9 @@ async function lint(project, options) {
|
|
|
1001
1090
|
const reported = reportedUse(sourceFile, diagnostic);
|
|
1002
1091
|
if (reported) found.push(reported);
|
|
1003
1092
|
}
|
|
1004
|
-
if (!
|
|
1005
|
-
found.push(...bracketValues(reader, file, sourceFile));
|
|
1006
|
-
found.push(...await
|
|
1093
|
+
if (!isExempt(sourceFile)) {
|
|
1094
|
+
found.push(...bracketValues(reader, file, sourceFile, isOwnSource));
|
|
1095
|
+
found.push(...await badValues(file, sourceFile));
|
|
1007
1096
|
}
|
|
1008
1097
|
}
|
|
1009
1098
|
return found.sort((a, b) => compareCodePoints(a.file, b.file) || a.line - b.line || a.column - b.column);
|
|
@@ -1021,4 +1110,4 @@ function report(diagnostics) {
|
|
|
1021
1110
|
return lines.map((line) => `${line}\n`).join("");
|
|
1022
1111
|
}
|
|
1023
1112
|
//#endregion
|
|
1024
|
-
export {
|
|
1113
|
+
export { resolveComponents as a, readConfig as c, relativeFile as i, report as n, ProjectError as o, compareCodePoints as r, openProject as s, lint as t };
|
package/dist/worker.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as readConfig, s as openProject, t as lint } from "./lint-Tvq5uHHS.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { promisify } from "node:util";
|
|
@@ -30,7 +30,7 @@ process.stdout.write(JSON.stringify(await check(${JSON.stringify(root)}, { save:
|
|
|
30
30
|
async function serve(root) {
|
|
31
31
|
const config = await readConfig(root);
|
|
32
32
|
if (!config.ok) throw new Error(messages(config.errors));
|
|
33
|
-
const { componentFolders } = config;
|
|
33
|
+
const { componentFolders, scan } = config;
|
|
34
34
|
let project = await openProject(root);
|
|
35
35
|
const unsaved = /* @__PURE__ */ new Map();
|
|
36
36
|
const outside = /* @__PURE__ */ new Set();
|
|
@@ -57,6 +57,7 @@ async function serve(root) {
|
|
|
57
57
|
}
|
|
58
58
|
return lint(project, {
|
|
59
59
|
componentFolders,
|
|
60
|
+
scan,
|
|
60
61
|
files: [file]
|
|
61
62
|
});
|
|
62
63
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rafters/veneer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Docs, rafters check and the composite compile for rafters projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/rafters-studio/veneer"
|
|
9
9
|
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"rafters-check": "./dist/bin.mjs"
|
|
12
|
+
},
|
|
10
13
|
"files": [
|
|
11
14
|
"dist"
|
|
12
15
|
],
|