@cmssy/eslint-plugin 12.4.0 → 12.5.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.
- package/dist/index.cjs +153 -17
- package/dist/index.js +155 -19
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9,9 +9,81 @@ var path = require('path');
|
|
|
9
9
|
var SERVER_MODULES = [/^@cmssy\/next\/server$/];
|
|
10
10
|
var SERVER_SYMBOLS = /* @__PURE__ */ new Set(["defineCmssyConfig"]);
|
|
11
11
|
var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
var aliasCache = /* @__PURE__ */ new Map();
|
|
13
|
+
function stripJsonComments(text) {
|
|
14
|
+
let out = "";
|
|
15
|
+
let inString = false;
|
|
16
|
+
let escaped = false;
|
|
17
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
18
|
+
const char = text[i];
|
|
19
|
+
if (inString) {
|
|
20
|
+
out += char;
|
|
21
|
+
if (escaped) escaped = false;
|
|
22
|
+
else if (char === "\\") escaped = true;
|
|
23
|
+
else if (char === '"') inString = false;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (char === '"') {
|
|
27
|
+
inString = true;
|
|
28
|
+
out += char;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === "/" && text[i + 1] === "/") {
|
|
32
|
+
while (i < text.length && text[i] !== "\n") i += 1;
|
|
33
|
+
out += "\n";
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (char === "/" && text[i + 1] === "*") {
|
|
37
|
+
i += 2;
|
|
38
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
39
|
+
i += 1;
|
|
40
|
+
}
|
|
41
|
+
i += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
out += char;
|
|
45
|
+
}
|
|
46
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
47
|
+
}
|
|
48
|
+
function readTsconfig(file) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(
|
|
51
|
+
stripJsonComments(fs.readFileSync(file, "utf8"))
|
|
52
|
+
);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function aliasConfigFor(fromFile) {
|
|
58
|
+
let dir = path.dirname(fromFile);
|
|
59
|
+
const visited = [];
|
|
60
|
+
for (let up = 0; up < 12; up += 1) {
|
|
61
|
+
const cached = aliasCache.get(dir);
|
|
62
|
+
if (cached !== void 0) {
|
|
63
|
+
for (const seen of visited) aliasCache.set(seen, cached);
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
66
|
+
visited.push(dir);
|
|
67
|
+
const candidate = path.resolve(dir, "tsconfig.json");
|
|
68
|
+
if (fs.existsSync(candidate)) {
|
|
69
|
+
const config = readTsconfig(candidate);
|
|
70
|
+
const options = config?.compilerOptions ?? {};
|
|
71
|
+
const paths = options.paths;
|
|
72
|
+
if (paths && Object.keys(paths).length > 0) {
|
|
73
|
+
const baseUrl = typeof options.baseUrl === "string" ? path.resolve(dir, options.baseUrl) : dir;
|
|
74
|
+
const found = { baseUrl, paths };
|
|
75
|
+
for (const seen of visited) aliasCache.set(seen, found);
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const parent = path.dirname(dir);
|
|
80
|
+
if (parent === dir) break;
|
|
81
|
+
dir = parent;
|
|
82
|
+
}
|
|
83
|
+
for (const seen of visited) aliasCache.set(seen, null);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function withExtension(base) {
|
|
15
87
|
for (const ext of EXTENSIONS) {
|
|
16
88
|
const candidate = `${base}${ext}`;
|
|
17
89
|
if (fs.existsSync(candidate) && !candidate.endsWith("/")) return candidate;
|
|
@@ -22,13 +94,45 @@ function resolveLocal(fromFile, specifier) {
|
|
|
22
94
|
}
|
|
23
95
|
return null;
|
|
24
96
|
}
|
|
97
|
+
function resolveAlias(fromFile, specifier) {
|
|
98
|
+
const config = aliasConfigFor(fromFile);
|
|
99
|
+
if (!config) return null;
|
|
100
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
101
|
+
const star = pattern.indexOf("*");
|
|
102
|
+
if (star === -1) {
|
|
103
|
+
if (pattern !== specifier) continue;
|
|
104
|
+
for (const target of targets) {
|
|
105
|
+
const hit = withExtension(path.resolve(config.baseUrl, target));
|
|
106
|
+
if (hit) return hit;
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const prefix = pattern.slice(0, star);
|
|
111
|
+
const suffix = pattern.slice(star + 1);
|
|
112
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) continue;
|
|
113
|
+
const middle = specifier.slice(
|
|
114
|
+
prefix.length,
|
|
115
|
+
specifier.length - suffix.length
|
|
116
|
+
);
|
|
117
|
+
for (const target of targets) {
|
|
118
|
+
const hit = withExtension(
|
|
119
|
+
path.resolve(config.baseUrl, target.replace("*", middle))
|
|
120
|
+
);
|
|
121
|
+
if (hit) return hit;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function resolveLocal(fromFile, specifier) {
|
|
127
|
+
if (specifier.startsWith(".")) {
|
|
128
|
+
return withExtension(path.resolve(path.dirname(fromFile), specifier));
|
|
129
|
+
}
|
|
130
|
+
return resolveAlias(fromFile, specifier);
|
|
131
|
+
}
|
|
25
132
|
function valueImports(code) {
|
|
26
|
-
const withoutTypes = code.replace(
|
|
27
|
-
/^\s*(?:import|export)\s+type\s[^;]*;/gm,
|
|
28
|
-
""
|
|
29
|
-
);
|
|
133
|
+
const withoutTypes = code.replace(/^\s*(?:import|export)\s+type\s[^;]*;/gm, "").replace(/\bimport\s*\([^)]*\)/g, "");
|
|
30
134
|
return [
|
|
31
|
-
...withoutTypes.matchAll(/(?:from|import)\s
|
|
135
|
+
...withoutTypes.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)
|
|
32
136
|
].map(([, specifier]) => specifier ?? "");
|
|
33
137
|
}
|
|
34
138
|
function importsServerConfig(code) {
|
|
@@ -43,6 +147,11 @@ function importsServerConfig(code) {
|
|
|
43
147
|
)
|
|
44
148
|
);
|
|
45
149
|
}
|
|
150
|
+
function hasDirective(source, directive) {
|
|
151
|
+
return new RegExp(
|
|
152
|
+
`^\\s*(?:\\/\\/[^\\n]*\\n|\\/\\*[\\s\\S]*?\\*\\/\\s*)*["']${directive}["']`
|
|
153
|
+
).test(source);
|
|
154
|
+
}
|
|
46
155
|
function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
|
|
47
156
|
if (seen.has(file)) return null;
|
|
48
157
|
seen.add(file);
|
|
@@ -55,6 +164,10 @@ function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
|
|
|
55
164
|
cache2.set(file, null);
|
|
56
165
|
return null;
|
|
57
166
|
}
|
|
167
|
+
if (hasDirective(code, "use server")) {
|
|
168
|
+
cache2.set(file, null);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
58
171
|
if (importsServerConfig(code)) {
|
|
59
172
|
const chain = [file];
|
|
60
173
|
cache2.set(file, chain);
|
|
@@ -73,10 +186,16 @@ function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
|
|
|
73
186
|
cache2.set(file, null);
|
|
74
187
|
return null;
|
|
75
188
|
}
|
|
76
|
-
function
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
189
|
+
function normalize(path$1) {
|
|
190
|
+
return path$1.split(path.sep).join("/");
|
|
191
|
+
}
|
|
192
|
+
function isClientEntry(filename, entries) {
|
|
193
|
+
const file = normalize(filename);
|
|
194
|
+
return entries.some((entry) => {
|
|
195
|
+
const wanted = normalize(entry);
|
|
196
|
+
if (path.isAbsolute(wanted)) return file === wanted;
|
|
197
|
+
return file === wanted || file.endsWith(`/${wanted}`);
|
|
198
|
+
});
|
|
80
199
|
}
|
|
81
200
|
var cache = /* @__PURE__ */ new Map();
|
|
82
201
|
var noServerConfigInClient = {
|
|
@@ -85,14 +204,31 @@ var noServerConfigInClient = {
|
|
|
85
204
|
docs: {
|
|
86
205
|
description: "Disallow a client component from importing values that read the cmssy server config"
|
|
87
206
|
},
|
|
88
|
-
schema: [
|
|
207
|
+
schema: [
|
|
208
|
+
{
|
|
209
|
+
type: "object",
|
|
210
|
+
properties: {
|
|
211
|
+
clientEntries: {
|
|
212
|
+
type: "array",
|
|
213
|
+
items: { type: "string" }
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
additionalProperties: false
|
|
217
|
+
}
|
|
218
|
+
],
|
|
89
219
|
messages: {
|
|
90
|
-
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page will fail at runtime with "missing required configuration".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.'
|
|
220
|
+
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page will fail at runtime with "missing required configuration".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.',
|
|
221
|
+
reachesConfigFromEntry: "This module is loaded in the browser, so it pulls the cmssy config there, where server env does not exist.\n {{chain}}\nImport the type instead (types are erased), or load the value with a dynamic import inside the function that needs it."
|
|
91
222
|
}
|
|
92
223
|
},
|
|
93
224
|
create(context) {
|
|
94
225
|
const filename = context.filename ?? context.getFilename();
|
|
95
|
-
|
|
226
|
+
const options = context.options[0] ?? {};
|
|
227
|
+
const entries = options.clientEntries ?? [];
|
|
228
|
+
const client = hasDirective(context.sourceCode.getText(), "use client");
|
|
229
|
+
const entry = isClientEntry(filename, entries);
|
|
230
|
+
if (!client && !entry) return {};
|
|
231
|
+
const messageId = client ? "reachesConfig" : "reachesConfigFromEntry";
|
|
96
232
|
return {
|
|
97
233
|
ImportDeclaration(node) {
|
|
98
234
|
const typed = node;
|
|
@@ -104,7 +240,7 @@ var noServerConfigInClient = {
|
|
|
104
240
|
if (SERVER_MODULES.some((pattern) => pattern.test(specifier))) {
|
|
105
241
|
context.report({
|
|
106
242
|
node,
|
|
107
|
-
messageId
|
|
243
|
+
messageId,
|
|
108
244
|
data: { chain: `${specifier} is server-only` }
|
|
109
245
|
});
|
|
110
246
|
return;
|
|
@@ -115,7 +251,7 @@ var noServerConfigInClient = {
|
|
|
115
251
|
if (!chain) return;
|
|
116
252
|
context.report({
|
|
117
253
|
node,
|
|
118
|
-
messageId
|
|
254
|
+
messageId,
|
|
119
255
|
data: {
|
|
120
256
|
chain: [filename, ...chain].map((f) => f.split("/").slice(-2).join("/")).join(" -> ")
|
|
121
257
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,85 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolve, dirname } from 'path';
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { isAbsolute, resolve, dirname, sep } from 'path';
|
|
3
3
|
|
|
4
4
|
// src/no-server-config-in-client.ts
|
|
5
5
|
var SERVER_MODULES = [/^@cmssy\/next\/server$/];
|
|
6
6
|
var SERVER_SYMBOLS = /* @__PURE__ */ new Set(["defineCmssyConfig"]);
|
|
7
7
|
var EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
var aliasCache = /* @__PURE__ */ new Map();
|
|
9
|
+
function stripJsonComments(text) {
|
|
10
|
+
let out = "";
|
|
11
|
+
let inString = false;
|
|
12
|
+
let escaped = false;
|
|
13
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
14
|
+
const char = text[i];
|
|
15
|
+
if (inString) {
|
|
16
|
+
out += char;
|
|
17
|
+
if (escaped) escaped = false;
|
|
18
|
+
else if (char === "\\") escaped = true;
|
|
19
|
+
else if (char === '"') inString = false;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (char === '"') {
|
|
23
|
+
inString = true;
|
|
24
|
+
out += char;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (char === "/" && text[i + 1] === "/") {
|
|
28
|
+
while (i < text.length && text[i] !== "\n") i += 1;
|
|
29
|
+
out += "\n";
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (char === "/" && text[i + 1] === "*") {
|
|
33
|
+
i += 2;
|
|
34
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
35
|
+
i += 1;
|
|
36
|
+
}
|
|
37
|
+
i += 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
out += char;
|
|
41
|
+
}
|
|
42
|
+
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
43
|
+
}
|
|
44
|
+
function readTsconfig(file) {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(
|
|
47
|
+
stripJsonComments(readFileSync(file, "utf8"))
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function aliasConfigFor(fromFile) {
|
|
54
|
+
let dir = dirname(fromFile);
|
|
55
|
+
const visited = [];
|
|
56
|
+
for (let up = 0; up < 12; up += 1) {
|
|
57
|
+
const cached = aliasCache.get(dir);
|
|
58
|
+
if (cached !== void 0) {
|
|
59
|
+
for (const seen of visited) aliasCache.set(seen, cached);
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
visited.push(dir);
|
|
63
|
+
const candidate = resolve(dir, "tsconfig.json");
|
|
64
|
+
if (existsSync(candidate)) {
|
|
65
|
+
const config = readTsconfig(candidate);
|
|
66
|
+
const options = config?.compilerOptions ?? {};
|
|
67
|
+
const paths = options.paths;
|
|
68
|
+
if (paths && Object.keys(paths).length > 0) {
|
|
69
|
+
const baseUrl = typeof options.baseUrl === "string" ? resolve(dir, options.baseUrl) : dir;
|
|
70
|
+
const found = { baseUrl, paths };
|
|
71
|
+
for (const seen of visited) aliasCache.set(seen, found);
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const parent = dirname(dir);
|
|
76
|
+
if (parent === dir) break;
|
|
77
|
+
dir = parent;
|
|
78
|
+
}
|
|
79
|
+
for (const seen of visited) aliasCache.set(seen, null);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function withExtension(base) {
|
|
11
83
|
for (const ext of EXTENSIONS) {
|
|
12
84
|
const candidate = `${base}${ext}`;
|
|
13
85
|
if (existsSync(candidate) && !candidate.endsWith("/")) return candidate;
|
|
@@ -18,13 +90,45 @@ function resolveLocal(fromFile, specifier) {
|
|
|
18
90
|
}
|
|
19
91
|
return null;
|
|
20
92
|
}
|
|
93
|
+
function resolveAlias(fromFile, specifier) {
|
|
94
|
+
const config = aliasConfigFor(fromFile);
|
|
95
|
+
if (!config) return null;
|
|
96
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
97
|
+
const star = pattern.indexOf("*");
|
|
98
|
+
if (star === -1) {
|
|
99
|
+
if (pattern !== specifier) continue;
|
|
100
|
+
for (const target of targets) {
|
|
101
|
+
const hit = withExtension(resolve(config.baseUrl, target));
|
|
102
|
+
if (hit) return hit;
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const prefix = pattern.slice(0, star);
|
|
107
|
+
const suffix = pattern.slice(star + 1);
|
|
108
|
+
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) continue;
|
|
109
|
+
const middle = specifier.slice(
|
|
110
|
+
prefix.length,
|
|
111
|
+
specifier.length - suffix.length
|
|
112
|
+
);
|
|
113
|
+
for (const target of targets) {
|
|
114
|
+
const hit = withExtension(
|
|
115
|
+
resolve(config.baseUrl, target.replace("*", middle))
|
|
116
|
+
);
|
|
117
|
+
if (hit) return hit;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function resolveLocal(fromFile, specifier) {
|
|
123
|
+
if (specifier.startsWith(".")) {
|
|
124
|
+
return withExtension(resolve(dirname(fromFile), specifier));
|
|
125
|
+
}
|
|
126
|
+
return resolveAlias(fromFile, specifier);
|
|
127
|
+
}
|
|
21
128
|
function valueImports(code) {
|
|
22
|
-
const withoutTypes = code.replace(
|
|
23
|
-
/^\s*(?:import|export)\s+type\s[^;]*;/gm,
|
|
24
|
-
""
|
|
25
|
-
);
|
|
129
|
+
const withoutTypes = code.replace(/^\s*(?:import|export)\s+type\s[^;]*;/gm, "").replace(/\bimport\s*\([^)]*\)/g, "");
|
|
26
130
|
return [
|
|
27
|
-
...withoutTypes.matchAll(/(?:from|import)\s
|
|
131
|
+
...withoutTypes.matchAll(/(?:from|import)\s*["']([^"']+)["']/g)
|
|
28
132
|
].map(([, specifier]) => specifier ?? "");
|
|
29
133
|
}
|
|
30
134
|
function importsServerConfig(code) {
|
|
@@ -39,6 +143,11 @@ function importsServerConfig(code) {
|
|
|
39
143
|
)
|
|
40
144
|
);
|
|
41
145
|
}
|
|
146
|
+
function hasDirective(source, directive) {
|
|
147
|
+
return new RegExp(
|
|
148
|
+
`^\\s*(?:\\/\\/[^\\n]*\\n|\\/\\*[\\s\\S]*?\\*\\/\\s*)*["']${directive}["']`
|
|
149
|
+
).test(source);
|
|
150
|
+
}
|
|
42
151
|
function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
|
|
43
152
|
if (seen.has(file)) return null;
|
|
44
153
|
seen.add(file);
|
|
@@ -51,6 +160,10 @@ function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
|
|
|
51
160
|
cache2.set(file, null);
|
|
52
161
|
return null;
|
|
53
162
|
}
|
|
163
|
+
if (hasDirective(code, "use server")) {
|
|
164
|
+
cache2.set(file, null);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
54
167
|
if (importsServerConfig(code)) {
|
|
55
168
|
const chain = [file];
|
|
56
169
|
cache2.set(file, chain);
|
|
@@ -69,10 +182,16 @@ function chainToServerConfig(file, cache2, seen = /* @__PURE__ */ new Set()) {
|
|
|
69
182
|
cache2.set(file, null);
|
|
70
183
|
return null;
|
|
71
184
|
}
|
|
72
|
-
function
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
185
|
+
function normalize(path) {
|
|
186
|
+
return path.split(sep).join("/");
|
|
187
|
+
}
|
|
188
|
+
function isClientEntry(filename, entries) {
|
|
189
|
+
const file = normalize(filename);
|
|
190
|
+
return entries.some((entry) => {
|
|
191
|
+
const wanted = normalize(entry);
|
|
192
|
+
if (isAbsolute(wanted)) return file === wanted;
|
|
193
|
+
return file === wanted || file.endsWith(`/${wanted}`);
|
|
194
|
+
});
|
|
76
195
|
}
|
|
77
196
|
var cache = /* @__PURE__ */ new Map();
|
|
78
197
|
var noServerConfigInClient = {
|
|
@@ -81,14 +200,31 @@ var noServerConfigInClient = {
|
|
|
81
200
|
docs: {
|
|
82
201
|
description: "Disallow a client component from importing values that read the cmssy server config"
|
|
83
202
|
},
|
|
84
|
-
schema: [
|
|
203
|
+
schema: [
|
|
204
|
+
{
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {
|
|
207
|
+
clientEntries: {
|
|
208
|
+
type: "array",
|
|
209
|
+
items: { type: "string" }
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
additionalProperties: false
|
|
213
|
+
}
|
|
214
|
+
],
|
|
85
215
|
messages: {
|
|
86
|
-
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page will fail at runtime with "missing required configuration".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.'
|
|
216
|
+
reachesConfig: 'This client component pulls the cmssy config into the browser bundle, where server env does not exist - the page will fail at runtime with "missing required configuration".\n {{chain}}\nImport the type instead (types are erased), or move the value into a module that does not touch the config.',
|
|
217
|
+
reachesConfigFromEntry: "This module is loaded in the browser, so it pulls the cmssy config there, where server env does not exist.\n {{chain}}\nImport the type instead (types are erased), or load the value with a dynamic import inside the function that needs it."
|
|
87
218
|
}
|
|
88
219
|
},
|
|
89
220
|
create(context) {
|
|
90
221
|
const filename = context.filename ?? context.getFilename();
|
|
91
|
-
|
|
222
|
+
const options = context.options[0] ?? {};
|
|
223
|
+
const entries = options.clientEntries ?? [];
|
|
224
|
+
const client = hasDirective(context.sourceCode.getText(), "use client");
|
|
225
|
+
const entry = isClientEntry(filename, entries);
|
|
226
|
+
if (!client && !entry) return {};
|
|
227
|
+
const messageId = client ? "reachesConfig" : "reachesConfigFromEntry";
|
|
92
228
|
return {
|
|
93
229
|
ImportDeclaration(node) {
|
|
94
230
|
const typed = node;
|
|
@@ -100,7 +236,7 @@ var noServerConfigInClient = {
|
|
|
100
236
|
if (SERVER_MODULES.some((pattern) => pattern.test(specifier))) {
|
|
101
237
|
context.report({
|
|
102
238
|
node,
|
|
103
|
-
messageId
|
|
239
|
+
messageId,
|
|
104
240
|
data: { chain: `${specifier} is server-only` }
|
|
105
241
|
});
|
|
106
242
|
return;
|
|
@@ -111,7 +247,7 @@ var noServerConfigInClient = {
|
|
|
111
247
|
if (!chain) return;
|
|
112
248
|
context.report({
|
|
113
249
|
node,
|
|
114
|
-
messageId
|
|
250
|
+
messageId,
|
|
115
251
|
data: {
|
|
116
252
|
chain: [filename, ...chain].map((f) => f.split("/").slice(-2).join("/")).join(" -> ")
|
|
117
253
|
}
|