nuxt-layerscope 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ import { u as LayerscopeConfig } from "./define-DNRGLVG_.mjs";
2
+ //#region src/config/effective.d.ts
3
+ /** The keys that describe the project; `buildDir` only says where to look. */
4
+ declare const PROJECT_CONFIG_KEYS: readonly ['layers', 'rules', 'ignore', 'globals'];
5
+ type ProjectConfig = Pick<LayerscopeConfig, (typeof PROJECT_CONFIG_KEYS)[number]>;
6
+ //#endregion
7
+ export { ProjectConfig as t };
@@ -0,0 +1,7 @@
1
+ import { ESLint, Linter } from "eslint";
2
+ //#region src/eslint/index.d.ts
3
+ declare const plugin: ESLint.Plugin & {
4
+ configs: Record<string, Linter.Config>;
5
+ };
6
+ //#endregion
7
+ export { plugin as default };
@@ -0,0 +1,213 @@
1
+ import { d as effectiveConfig, l as REGISTRY_FILE } from "./layer-factory-xZmIAPrA.mjs";
2
+ import { t as packageVersion } from "./version-D6whhsHH.mjs";
3
+ import { _ as findConfigFile, a as lineColumn, c as createOwnerLookup, d as createAnalysisEnv, f as readRegistry, h as boundaryFindings, i as isCheckedFile, l as nameLayers, o as offsetAt, p as tableFromRegistry, s as scanFile, t as FileAnalysis, y as loadConfigSync } from "./file-analysis-Bkq_69x4.mjs";
4
+ import { dirname, join, relative, resolve } from "pathe";
5
+ import { existsSync, readFileSync, statSync } from "node:fs";
6
+ //#region src/eslint/lint-file.ts
7
+ /** Both rules lint the same text, so the last result per file is reused. */
8
+ const lastLint = /* @__PURE__ */ new Map();
9
+ /**
10
+ * oxlint passes only the `<script>` of a `.vue` file to JS plugins, while the template matters
11
+ * too; the saved file is read instead.
12
+ */
13
+ function fullSource(file, text) {
14
+ return file.endsWith(".vue") && !/<(?:script|template)[\s>]/u.test(text) ? readFileSync(file, "utf8") : text;
15
+ }
16
+ /** Lints the text the editor holds, which may differ from the file on disk. */
17
+ function lintFile(file, text, project) {
18
+ const source = fullSource(file, text);
19
+ const cached = lastLint.get(file);
20
+ if (cached?.source === source) return cached.lint;
21
+ const layer = project.env.ownerOf(file);
22
+ let lint = {
23
+ source,
24
+ boundary: [],
25
+ unresolved: []
26
+ };
27
+ if (layer !== null && isCheckedFile(file, layer, project.config.ignore ?? [])) {
28
+ const analysis = new FileAnalysis(file, source, layer, project.env).run(scanFile(source, file));
29
+ lint = {
30
+ source,
31
+ boundary: boundaryFindings(analysis.edges, project.config),
32
+ unresolved: analysis.unresolved
33
+ };
34
+ }
35
+ lastLint.set(file, {
36
+ source,
37
+ lint
38
+ });
39
+ return lint;
40
+ }
41
+ //#endregion
42
+ //#region src/eslint/location.ts
43
+ /**
44
+ * Maps a finding from the full file into the text the linter passed. ESLint passes the full
45
+ * file; oxlint passes only a `.vue` file's `<script>`, where template findings have no position.
46
+ */
47
+ function reportLocation(finding, source, text) {
48
+ if (source === text) return {
49
+ line: finding.line,
50
+ column: finding.column,
51
+ outside: false
52
+ };
53
+ const base = source.indexOf(text);
54
+ const offset = offsetAt(source, finding) - base;
55
+ if (base === -1 || offset < 0 || offset > text.length) return {
56
+ line: 1,
57
+ column: 1,
58
+ outside: true
59
+ };
60
+ return {
61
+ ...lineColumn(text, offset),
62
+ outside: false
63
+ };
64
+ }
65
+ //#endregion
66
+ //#region src/eslint/load-project.ts
67
+ /** Config, layers and symbols of a project, from its registry; Nuxt itself is never loaded. */
68
+ function loadProject(rootDir) {
69
+ const fileConfig = loadConfigSync(rootDir);
70
+ const buildDir = resolve(rootDir, fileConfig.buildDir ?? ".nuxt");
71
+ const read = readRegistry(buildDir);
72
+ if (read.kind !== "ok") return {
73
+ ok: false,
74
+ message: `${read.file} is missing or from another nuxt-layerscope release. Add "nuxt-layerscope" to "modules" in nuxt.config and run "nuxi prepare".`
75
+ };
76
+ const config = effectiveConfig(fileConfig, read.registry.config);
77
+ const layers = nameLayers(read.registry.layers, rootDir, config);
78
+ const table = tableFromRegistry(read.registry, buildDir);
79
+ const lintConfig = {
80
+ ...config,
81
+ rules: {
82
+ ...config.rules,
83
+ "layer-boundary": "error",
84
+ "unresolved-reference": "warn"
85
+ }
86
+ };
87
+ return {
88
+ ok: true,
89
+ project: {
90
+ rootDir,
91
+ config: lintConfig,
92
+ env: createAnalysisEnv(table, createOwnerLookup(layers), buildDir, lintConfig)
93
+ }
94
+ };
95
+ }
96
+ //#endregion
97
+ //#region src/eslint/project.ts
98
+ const DEFAULT_REGISTRY = join(".nuxt", REGISTRY_FILE);
99
+ /** Editors keep the linter running, so a project is reloaded when its inputs change. */
100
+ const cache = /* @__PURE__ */ new Map();
101
+ function findRoot(file) {
102
+ for (let dir = dirname(file);; dir = dirname(dir)) {
103
+ if (existsSync(join(dir, DEFAULT_REGISTRY))) return dir;
104
+ if (dirname(dir) === dir) return null;
105
+ }
106
+ }
107
+ function mtime(file) {
108
+ return file !== null && existsSync(file) ? statSync(file).mtimeMs : 0;
109
+ }
110
+ function tryLoadProject(rootDir) {
111
+ try {
112
+ return loadProject(rootDir);
113
+ } catch (error) {
114
+ return {
115
+ ok: false,
116
+ message: error.message
117
+ };
118
+ }
119
+ }
120
+ /** The Nuxt project a linted file belongs to, from its registry; Nuxt itself is never loaded. */
121
+ function projectFor(file, root) {
122
+ const rootDir = root === void 0 ? findRoot(file) : resolve(root);
123
+ if (rootDir === null) return {
124
+ ok: false,
125
+ message: `No ${DEFAULT_REGISTRY} above this file. Run "nuxi prepare", or set the rule's "root" option to the Nuxt project.`
126
+ };
127
+ const configFile = findConfigFile(rootDir);
128
+ const registry = join(rootDir, DEFAULT_REGISTRY);
129
+ const stamp = `${mtime(configFile)}:${mtime(registry)}`;
130
+ const cached = cache.get(rootDir);
131
+ if (cached?.stamp === stamp) return cached.load;
132
+ const load = tryLoadProject(rootDir);
133
+ cache.set(rootDir, {
134
+ stamp,
135
+ load
136
+ });
137
+ return load;
138
+ }
139
+ //#endregion
140
+ //#region src/eslint/rules.ts
141
+ const OPTIONS_SCHEMA = [{
142
+ type: "object",
143
+ properties: { root: { type: "string" } },
144
+ additionalProperties: false
145
+ }];
146
+ function describeFinding(finding, rootDir) {
147
+ if (finding.rule !== "layer-boundary" || finding.target === null) return finding.message;
148
+ const allowed = finding.allowed === void 0 || finding.allowed.length === 0 ? "no other layers" : finding.allowed.join(", ");
149
+ return `${finding.message}: ${finding.symbol} → ${relative(rootDir, finding.target)} (allowed for "${finding.fromLayer}": ${allowed})`;
150
+ }
151
+ function createRule(description, pick, reportsSetup) {
152
+ return {
153
+ meta: {
154
+ type: "problem",
155
+ docs: { description },
156
+ schema: OPTIONS_SCHEMA
157
+ },
158
+ create(context) {
159
+ const [options] = context.options;
160
+ return { "Program:exit"() {
161
+ const load = projectFor(context.filename, options?.root);
162
+ if (!load.ok) {
163
+ if (reportsSetup) context.report({
164
+ loc: {
165
+ line: 1,
166
+ column: 0
167
+ },
168
+ message: `layerscope: ${load.message}`
169
+ });
170
+ return;
171
+ }
172
+ const { project } = load;
173
+ const { text } = context.sourceCode;
174
+ const lint = lintFile(context.filename, text, project);
175
+ for (const finding of pick(lint)) {
176
+ const { line, column, outside } = reportLocation(finding, lint.source, text);
177
+ const message = describeFinding(finding, project.rootDir);
178
+ context.report({
179
+ loc: {
180
+ line,
181
+ column: column - 1
182
+ },
183
+ message: outside ? `${message} (template line ${finding.line})` : message
184
+ });
185
+ }
186
+ } };
187
+ }
188
+ };
189
+ }
190
+ const rules = {
191
+ "layer-boundary": createRule("Disallow dependencies, including auto-imports, into layers this layer may not use", (lint) => lint.boundary, true),
192
+ "unresolved-reference": createRule("Report identifiers, components and imports that cannot be resolved", (lint) => lint.unresolved, false)
193
+ };
194
+ //#endregion
195
+ //#region src/eslint/index.ts
196
+ const plugin = {
197
+ meta: {
198
+ name: "nuxt-layerscope",
199
+ version: packageVersion()
200
+ },
201
+ rules,
202
+ configs: {}
203
+ };
204
+ plugin.configs.recommended = {
205
+ name: "nuxt-layerscope/recommended",
206
+ plugins: { layerscope: plugin },
207
+ rules: {
208
+ "layerscope/layer-boundary": "error",
209
+ "layerscope/unresolved-reference": "warn"
210
+ }
211
+ };
212
+ //#endregion
213
+ export { plugin as default };