arkgate 2.12.0 → 2.13.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/CHANGELOG.md +83 -0
- package/README.md +55 -31
- package/bin/ark-check.mjs +95 -36
- package/bin/ark-mcp.mjs +11 -5
- package/bin/ark-shared.mjs +88 -56
- package/bin/ark.mjs +45 -10
- package/bin/lib/agent-gates.mjs +12 -0
- package/bin/lib/architecture-scan.mjs +8 -0
- package/bin/lib/ci-and-commands.mjs +9 -3
- package/bin/lib/codex-home.mjs +7 -0
- package/bin/lib/config-contract.mjs +331 -0
- package/bin/lib/doctor-plan.mjs +43 -16
- package/bin/lib/enforcement-profiles.mjs +97 -0
- package/bin/lib/host-support-matrix.mjs +77 -0
- package/bin/lib/install-migrate.mjs +45 -14
- package/bin/lib/mcp-adoption.mjs +35 -3
- package/bin/lib/open-html.mjs +75 -0
- package/bin/lib/presets.mjs +3 -2
- package/bin/lib/safety-diagnostics.mjs +31 -11
- package/bin/lib/skill-install.mjs +64 -0
- package/bin/lib/ts-resolve.mjs +2 -1
- package/bin/lib/weakest-link.mjs +417 -0
- package/bin/lib/write-path-capabilities.mjs +182 -0
- package/bin/lib/write-path-detect.mjs +62 -99
- package/dist/configContract-iBLxx5Tz.d.cts +53 -0
- package/dist/configContract-iBLxx5Tz.d.ts +53 -0
- package/dist/eslint/index.cjs +375 -13
- package/dist/eslint/index.cjs.map +1 -1
- package/dist/eslint/index.d.cts +30 -20
- package/dist/eslint/index.d.ts +30 -20
- package/dist/eslint/index.js +375 -13
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.cjs +723 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -5
- package/dist/index.d.ts +95 -5
- package/dist/index.js +716 -61
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +150 -42
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +2 -1
- package/dist/nestjs/index.d.ts +2 -1
- package/dist/nestjs/index.js +150 -42
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +723 -61
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +3 -2
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.js +716 -61
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-BZ17b9i5.d.cts → types-BxBwnBpC.d.cts} +9 -36
- package/dist/{types-BZ17b9i5.d.ts → types-Wcs_l1_J.d.ts} +9 -36
- package/docs/agent-guide.md +32 -20
- package/docs/ai-gates.md +53 -18
- package/docs/configuration.md +97 -0
- package/docs/enthusiast/README.md +3 -3
- package/docs/enthusiast/how-to-agent-gates.md +7 -3
- package/docs/migrate-from-ark-runtime-kernel.md +3 -0
- package/docs/package-surface.md +14 -9
- package/docs/production-hardening.md +15 -2
- package/docs/threat-model.md +65 -0
- package/docs/typescript-support.md +3 -3
- package/package.json +15 -2
- package/schemas/ark.config.schema.json +750 -0
- package/server.json +2 -2
- package/templates/hooks/pre-commit-ark +37 -0
- package/templates/skills/ark-coverage.md +2 -2
- package/templates/skills/ark-runtime.md +8 -5
- package/templates/skills/ark-upgrade.md +36 -16
- package/tests/fixtures/ts-consumer/ark.config.json +2 -0
package/dist/eslint/index.js
CHANGED
|
@@ -170,6 +170,311 @@ function isEdgeDenied(rules2, from, to, options) {
|
|
|
170
170
|
return findDeniedEdgeRule(rules2, from, to, options) !== void 0;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// src/domain/configContract.ts
|
|
174
|
+
var ARK_CONFIG_SCHEMA_VERSION = "1.0";
|
|
175
|
+
var ARK_CONFIG_SCHEMA_URL = "https://unpkg.com/arkgate@2/schemas/ark.config.schema.json";
|
|
176
|
+
var DEFAULT_LAYER_NAMES = [
|
|
177
|
+
"DomainModel",
|
|
178
|
+
"ApplicationOrchestration",
|
|
179
|
+
"PersistenceAdapters",
|
|
180
|
+
"IntegrationAdapters",
|
|
181
|
+
"WorkflowSagaEngine",
|
|
182
|
+
"BackgroundJobsScheduling",
|
|
183
|
+
"PresentationAdapters",
|
|
184
|
+
"ReportingReadModels",
|
|
185
|
+
"ExtensibilityMetadata",
|
|
186
|
+
"SecurityAuditObservability",
|
|
187
|
+
"Kernel"
|
|
188
|
+
];
|
|
189
|
+
var DEFAULT_ALLOWED_FLOWS = /* @__PURE__ */ new Set([
|
|
190
|
+
"PresentationAdapters->ApplicationOrchestration",
|
|
191
|
+
"ApplicationOrchestration->DomainModel",
|
|
192
|
+
"WorkflowSagaEngine->ApplicationOrchestration",
|
|
193
|
+
"WorkflowSagaEngine->DomainModel",
|
|
194
|
+
"BackgroundJobsScheduling->ApplicationOrchestration"
|
|
195
|
+
]);
|
|
196
|
+
function createDefaultRules() {
|
|
197
|
+
const rules2 = [];
|
|
198
|
+
for (const from of DEFAULT_LAYER_NAMES) {
|
|
199
|
+
for (const to of DEFAULT_LAYER_NAMES) {
|
|
200
|
+
if (from === to || DEFAULT_ALLOWED_FLOWS.has(`${from}->${to}`)) continue;
|
|
201
|
+
rules2.push({ from, to, allowed: false });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return rules2;
|
|
205
|
+
}
|
|
206
|
+
var DEFAULT_ARK_CONFIG_RULES = createDefaultRules();
|
|
207
|
+
var stringArraySchema = {
|
|
208
|
+
type: "array",
|
|
209
|
+
items: { type: "string", minLength: 1 },
|
|
210
|
+
uniqueItems: true
|
|
211
|
+
};
|
|
212
|
+
var ARK_CONFIG_SCHEMA = {
|
|
213
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
214
|
+
$id: ARK_CONFIG_SCHEMA_URL,
|
|
215
|
+
title: "ArkGate architecture contract",
|
|
216
|
+
description: "Versioned contract consumed identically by ArkGate CLI, MCP, and ESLint surfaces.",
|
|
217
|
+
type: "object",
|
|
218
|
+
additionalProperties: false,
|
|
219
|
+
required: ["$schema", "schemaVersion", "include", "layers", "rules"],
|
|
220
|
+
properties: {
|
|
221
|
+
$schema: {
|
|
222
|
+
type: "string",
|
|
223
|
+
minLength: 1,
|
|
224
|
+
default: ARK_CONFIG_SCHEMA_URL,
|
|
225
|
+
description: "Editor-facing URL or local path for this JSON Schema."
|
|
226
|
+
},
|
|
227
|
+
schemaVersion: {
|
|
228
|
+
type: "string",
|
|
229
|
+
const: ARK_CONFIG_SCHEMA_VERSION,
|
|
230
|
+
default: ARK_CONFIG_SCHEMA_VERSION
|
|
231
|
+
},
|
|
232
|
+
name: { type: "string", minLength: 1 },
|
|
233
|
+
include: { ...stringArraySchema, minItems: 1, default: ["src"] },
|
|
234
|
+
exclude: { ...stringArraySchema, default: [] },
|
|
235
|
+
excludeGenerated: { type: "boolean", default: true },
|
|
236
|
+
frameworkOverlay: { type: "string", minLength: 1 },
|
|
237
|
+
layers: {
|
|
238
|
+
type: "array",
|
|
239
|
+
default: [],
|
|
240
|
+
items: { $ref: "#/$defs/layer" }
|
|
241
|
+
},
|
|
242
|
+
rules: {
|
|
243
|
+
type: "array",
|
|
244
|
+
default: DEFAULT_ARK_CONFIG_RULES,
|
|
245
|
+
items: { $ref: "#/$defs/rule" }
|
|
246
|
+
},
|
|
247
|
+
cyclePolicy: {
|
|
248
|
+
type: "string",
|
|
249
|
+
enum: ["strict", "soft", "framework-soft", "off"],
|
|
250
|
+
default: "strict"
|
|
251
|
+
},
|
|
252
|
+
dynamicImportAllowlist: { ...stringArraySchema, default: [] },
|
|
253
|
+
safety: {
|
|
254
|
+
$ref: "#/$defs/safety",
|
|
255
|
+
default: {
|
|
256
|
+
maxTsSuppressions: 0,
|
|
257
|
+
maxAnyCasts: 0,
|
|
258
|
+
allowInMemory: false,
|
|
259
|
+
allowDisabledPeerIsolation: false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
$defs: {
|
|
264
|
+
layer: {
|
|
265
|
+
type: "object",
|
|
266
|
+
additionalProperties: false,
|
|
267
|
+
required: ["name", "patterns"],
|
|
268
|
+
properties: {
|
|
269
|
+
name: { type: "string", minLength: 1 },
|
|
270
|
+
patterns: { ...stringArraySchema, minItems: 1 },
|
|
271
|
+
exclude: stringArraySchema,
|
|
272
|
+
intentPrefixes: stringArraySchema,
|
|
273
|
+
description: { type: "string", minLength: 1 },
|
|
274
|
+
forbiddenGlobals: stringArraySchema,
|
|
275
|
+
mayImportInfrastructure: { type: "boolean" },
|
|
276
|
+
optional: { type: "boolean" }
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
rule: {
|
|
280
|
+
type: "object",
|
|
281
|
+
additionalProperties: false,
|
|
282
|
+
required: ["from", "to", "allowed"],
|
|
283
|
+
properties: {
|
|
284
|
+
from: { type: "string", minLength: 1 },
|
|
285
|
+
to: { type: "string", minLength: 1 },
|
|
286
|
+
allowed: { type: "boolean" },
|
|
287
|
+
message: { type: "string", minLength: 1 },
|
|
288
|
+
peerIsolation: { type: "boolean" },
|
|
289
|
+
sliceFolders: { ...stringArraySchema, minItems: 1 }
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
safety: {
|
|
293
|
+
type: "object",
|
|
294
|
+
additionalProperties: false,
|
|
295
|
+
properties: {
|
|
296
|
+
maxTsSuppressions: { type: "integer", minimum: 0, default: 0 },
|
|
297
|
+
maxAnyCasts: { type: "integer", minimum: 0, default: 0 },
|
|
298
|
+
allowInMemory: { type: "boolean", default: false },
|
|
299
|
+
allowDisabledPeerIsolation: { type: "boolean", default: false }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
var ArkConfigValidationError = class extends Error {
|
|
305
|
+
issues;
|
|
306
|
+
source;
|
|
307
|
+
constructor(source, issues) {
|
|
308
|
+
super(
|
|
309
|
+
`Invalid ArkGate config (${source}):
|
|
310
|
+
${issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n")}`
|
|
311
|
+
);
|
|
312
|
+
this.name = "ArkConfigValidationError";
|
|
313
|
+
this.source = source;
|
|
314
|
+
this.issues = issues;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
function isObject(value) {
|
|
318
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
319
|
+
}
|
|
320
|
+
function propertyPath(parent, key) {
|
|
321
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
|
|
322
|
+
}
|
|
323
|
+
function valueType(value) {
|
|
324
|
+
if (value === null) return "null";
|
|
325
|
+
if (Array.isArray(value)) return "array";
|
|
326
|
+
return typeof value;
|
|
327
|
+
}
|
|
328
|
+
function resolveSchemaRef(ref, root) {
|
|
329
|
+
const prefix = "#/$defs/";
|
|
330
|
+
if (!ref.startsWith(prefix)) return void 0;
|
|
331
|
+
return root.$defs[ref.slice(prefix.length)];
|
|
332
|
+
}
|
|
333
|
+
function validateNode(value, schema, path2, root, issues) {
|
|
334
|
+
if (schema.$ref) {
|
|
335
|
+
const referenced = resolveSchemaRef(schema.$ref, root);
|
|
336
|
+
if (!referenced) {
|
|
337
|
+
issues.push({ path: path2, message: `schema reference ${schema.$ref} cannot be resolved` });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
validateNode(value, referenced, path2, root, issues);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (schema.const !== void 0 && !Object.is(value, schema.const)) {
|
|
344
|
+
issues.push({ path: path2, message: `must equal ${JSON.stringify(schema.const)}` });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
|
|
348
|
+
issues.push({ path: path2, message: `must be one of ${schema.enum.map(String).join(", ")}` });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (schema.type === "object") {
|
|
352
|
+
if (!isObject(value)) {
|
|
353
|
+
issues.push({ path: path2, message: `must be an object; received ${valueType(value)}` });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const properties = schema.properties ?? {};
|
|
357
|
+
for (const key of schema.required ?? []) {
|
|
358
|
+
if (value[key] === void 0) {
|
|
359
|
+
issues.push({ path: propertyPath(path2, key), message: "is required" });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (schema.additionalProperties === false) {
|
|
363
|
+
for (const key of Object.keys(value)) {
|
|
364
|
+
if (!(key in properties)) {
|
|
365
|
+
issues.push({ path: propertyPath(path2, key), message: "unknown field" });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
370
|
+
if (value[key] !== void 0) {
|
|
371
|
+
validateNode(value[key], childSchema, propertyPath(path2, key), root, issues);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (schema.type === "array") {
|
|
377
|
+
if (!Array.isArray(value)) {
|
|
378
|
+
issues.push({ path: path2, message: `must be an array; received ${valueType(value)}` });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
382
|
+
issues.push({ path: path2, message: `must contain at least ${schema.minItems} item(s)` });
|
|
383
|
+
}
|
|
384
|
+
if (schema.uniqueItems) {
|
|
385
|
+
const serialized = value.map((entry) => JSON.stringify(entry));
|
|
386
|
+
if (new Set(serialized).size !== serialized.length) {
|
|
387
|
+
issues.push({ path: path2, message: "must not contain duplicate items" });
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (schema.items) {
|
|
391
|
+
value.forEach(
|
|
392
|
+
(entry, index) => validateNode(entry, schema.items, `${path2}[${index}]`, root, issues)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (schema.type === "string") {
|
|
398
|
+
if (typeof value !== "string") {
|
|
399
|
+
issues.push({ path: path2, message: `must be a string; received ${valueType(value)}` });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
403
|
+
issues.push({ path: path2, message: `must contain at least ${schema.minLength} character(s)` });
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (schema.type === "boolean") {
|
|
408
|
+
if (typeof value !== "boolean") {
|
|
409
|
+
issues.push({ path: path2, message: `must be a boolean; received ${valueType(value)}` });
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (schema.type === "integer") {
|
|
414
|
+
if (!Number.isInteger(value)) {
|
|
415
|
+
issues.push({ path: path2, message: `must be an integer; received ${valueType(value)}` });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
419
|
+
issues.push({ path: path2, message: `must be at least ${schema.minimum}` });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function defaultedConfig(input) {
|
|
424
|
+
return {
|
|
425
|
+
...input,
|
|
426
|
+
$schema: input.$schema === void 0 ? ARK_CONFIG_SCHEMA_URL : input.$schema,
|
|
427
|
+
schemaVersion: input.schemaVersion === void 0 ? ARK_CONFIG_SCHEMA_VERSION : input.schemaVersion,
|
|
428
|
+
include: input.include === void 0 ? ["src"] : input.include,
|
|
429
|
+
layers: input.layers === void 0 ? [] : input.layers,
|
|
430
|
+
rules: input.rules === void 0 ? DEFAULT_ARK_CONFIG_RULES.map((rule) => ({ ...rule })) : input.rules
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function migrateArkConfig(input, source = "ark.config.json") {
|
|
434
|
+
if (!isObject(input)) {
|
|
435
|
+
throw new ArkConfigValidationError(source, [
|
|
436
|
+
{ path: "$", message: `must be an object; received ${valueType(input)}` }
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
const migratedFrom = input.schemaVersion === void 0 ? "unversioned" : null;
|
|
440
|
+
if (input.schemaVersion !== void 0 && input.schemaVersion !== ARK_CONFIG_SCHEMA_VERSION) {
|
|
441
|
+
throw new ArkConfigValidationError(source, [
|
|
442
|
+
{
|
|
443
|
+
path: "$.schemaVersion",
|
|
444
|
+
message: `unsupported version ${JSON.stringify(input.schemaVersion)}; expected ${ARK_CONFIG_SCHEMA_VERSION}`
|
|
445
|
+
}
|
|
446
|
+
]);
|
|
447
|
+
}
|
|
448
|
+
return { candidate: defaultedConfig(input), migratedFrom };
|
|
449
|
+
}
|
|
450
|
+
function loadArkConfigContract(input, source = "ark.config.json") {
|
|
451
|
+
const { candidate, migratedFrom } = migrateArkConfig(input, source);
|
|
452
|
+
const issues = [];
|
|
453
|
+
validateNode(
|
|
454
|
+
candidate,
|
|
455
|
+
ARK_CONFIG_SCHEMA,
|
|
456
|
+
"$",
|
|
457
|
+
ARK_CONFIG_SCHEMA,
|
|
458
|
+
issues
|
|
459
|
+
);
|
|
460
|
+
if (issues.length > 0) throw new ArkConfigValidationError(source, issues);
|
|
461
|
+
return { config: candidate, migratedFrom };
|
|
462
|
+
}
|
|
463
|
+
function parseArkConfigJson(json, source = "ark.config.json") {
|
|
464
|
+
let input;
|
|
465
|
+
try {
|
|
466
|
+
input = JSON.parse(json);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw new ArkConfigValidationError(source, [
|
|
469
|
+
{
|
|
470
|
+
path: "$",
|
|
471
|
+
message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
472
|
+
}
|
|
473
|
+
]);
|
|
474
|
+
}
|
|
475
|
+
return loadArkConfigContract(input, source);
|
|
476
|
+
}
|
|
477
|
+
|
|
173
478
|
// src/eslint/index.ts
|
|
174
479
|
function lintedFilename(context) {
|
|
175
480
|
if (typeof context.physicalFilename === "string" && context.physicalFilename.length > 0) {
|
|
@@ -201,14 +506,10 @@ function findConfigPath(startFile) {
|
|
|
201
506
|
var _configCache = /* @__PURE__ */ new Map();
|
|
202
507
|
function loadArkConfig(configPath) {
|
|
203
508
|
if (_configCache.has(configPath)) return _configCache.get(configPath) ?? null;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} catch {
|
|
209
|
-
_configCache.set(configPath, null);
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
509
|
+
if (!fs.existsSync(configPath)) return null;
|
|
510
|
+
const config = parseArkConfigJson(fs.readFileSync(configPath, "utf8"), configPath).config;
|
|
511
|
+
_configCache.set(configPath, config);
|
|
512
|
+
return config;
|
|
212
513
|
}
|
|
213
514
|
function resolveRelativeImport(fromFile, specifier) {
|
|
214
515
|
if (!specifier.startsWith(".")) return null;
|
|
@@ -239,6 +540,46 @@ function stringValue(node) {
|
|
|
239
540
|
function propertyName(node) {
|
|
240
541
|
return node?.name ?? stringValue(node);
|
|
241
542
|
}
|
|
543
|
+
function sourceCodeFor(context) {
|
|
544
|
+
return context.sourceCode ?? context.getSourceCode?.();
|
|
545
|
+
}
|
|
546
|
+
function referenceFor(context, node) {
|
|
547
|
+
let scope = sourceCodeFor(context)?.getScope?.(node);
|
|
548
|
+
while (scope) {
|
|
549
|
+
const reference = scope.references?.find((candidate) => candidate.identifier === node);
|
|
550
|
+
if (reference) return reference;
|
|
551
|
+
scope = scope.upper ?? void 0;
|
|
552
|
+
}
|
|
553
|
+
return void 0;
|
|
554
|
+
}
|
|
555
|
+
function isLocallyBound(context, node, name) {
|
|
556
|
+
const reference = referenceFor(context, node);
|
|
557
|
+
if (reference?.resolved) return (reference.resolved.defs?.length ?? 0) > 0;
|
|
558
|
+
let scope = sourceCodeFor(context)?.getScope?.(node);
|
|
559
|
+
while (scope) {
|
|
560
|
+
const variable = scope.set?.get(name);
|
|
561
|
+
if (variable) return (variable.defs?.length ?? 0) > 0;
|
|
562
|
+
scope = scope.upper ?? void 0;
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
function isValueIdentifierReference(context, node) {
|
|
567
|
+
const reference = referenceFor(context, node);
|
|
568
|
+
if (reference) return reference.isValueReference !== false;
|
|
569
|
+
return node.parent?.type === "VariableDeclarator" && node.parent.init === node;
|
|
570
|
+
}
|
|
571
|
+
function memberExpressionPath(node) {
|
|
572
|
+
if (node?.type === "Identifier" && node.name) {
|
|
573
|
+
return { root: node, segments: [node.name] };
|
|
574
|
+
}
|
|
575
|
+
if (!node) return void 0;
|
|
576
|
+
const memberLike = node.type === "MemberExpression" || Boolean(node.object && node.property);
|
|
577
|
+
if (!memberLike || node.computed === true) return void 0;
|
|
578
|
+
const base = memberExpressionPath(node.object);
|
|
579
|
+
const property = propertyName(node.property);
|
|
580
|
+
if (!base || !property) return void 0;
|
|
581
|
+
return { root: base.root, segments: [...base.segments, property] };
|
|
582
|
+
}
|
|
242
583
|
function calleePropertyName(node) {
|
|
243
584
|
return propertyName(node.callee?.property);
|
|
244
585
|
}
|
|
@@ -431,6 +772,7 @@ var noForbiddenGlobals = {
|
|
|
431
772
|
if (!globals) {
|
|
432
773
|
return {};
|
|
433
774
|
}
|
|
775
|
+
const scopeAware = typeof sourceCodeFor(context)?.getScope === "function";
|
|
434
776
|
const report = (node, name) => context.report({
|
|
435
777
|
node,
|
|
436
778
|
messageId: config ? "forbiddenGlobal" : "forbiddenGlobalDefault",
|
|
@@ -438,19 +780,39 @@ var noForbiddenGlobals = {
|
|
|
438
780
|
});
|
|
439
781
|
return {
|
|
440
782
|
MemberExpression(node) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
783
|
+
if (node.parent?.type === "MemberExpression" && node.parent.object === node) return;
|
|
784
|
+
const path2 = memberExpressionPath(node);
|
|
785
|
+
if (!path2 || isLocallyBound(context, path2.root, path2.segments[0])) return;
|
|
786
|
+
const explicitGlobalThis = path2.segments[0] === "globalThis";
|
|
787
|
+
const normalized = explicitGlobalThis ? path2.segments.slice(1) : path2.segments;
|
|
788
|
+
let match;
|
|
789
|
+
for (let length = normalized.length; length >= (explicitGlobalThis ? 1 : 2); length -= 1) {
|
|
790
|
+
const candidate = normalized.slice(0, length).join(".");
|
|
791
|
+
if (globals.has(candidate)) {
|
|
792
|
+
match = candidate;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (match) report(node, match);
|
|
797
|
+
else if (!scopeAware && globals.has(path2.segments[0])) {
|
|
798
|
+
report(node, path2.segments[0]);
|
|
799
|
+
}
|
|
446
800
|
},
|
|
447
801
|
CallExpression(node) {
|
|
802
|
+
if (scopeAware) return;
|
|
448
803
|
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
449
804
|
if (callee && globals.has(callee)) report(node, callee);
|
|
450
805
|
},
|
|
451
806
|
NewExpression(node) {
|
|
807
|
+
if (scopeAware) return;
|
|
452
808
|
const callee = node.callee?.type === "Identifier" ? node.callee.name : void 0;
|
|
453
809
|
if (callee && globals.has(callee)) report(node, callee);
|
|
810
|
+
},
|
|
811
|
+
Identifier(node) {
|
|
812
|
+
if (!scopeAware || !node.name || !globals.has(node.name) || !isValueIdentifierReference(context, node) || isLocallyBound(context, node, node.name)) {
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
report(node, node.name);
|
|
454
816
|
}
|
|
455
817
|
};
|
|
456
818
|
}
|