@orkestrel/scaffold 0.0.107 → 0.0.109
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/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
- package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +368 -27
- package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
- package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
- package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
- package/dist/agents/templates/brief.md +1 -1
- package/dist/bin/main.js +51 -46
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +26 -20
- package/dist/host/agents/orchestration.md +98 -58
- package/dist/host/agents/platforms/posix.md +15 -0
- package/dist/host/agents/platforms/windows.md +37 -0
- package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
- package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
- package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
- package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +12 -14
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +36 -24
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +14 -11
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +428 -27
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
- package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
- package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +91 -30
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +34 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
- package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
- package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
- package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -10
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
- package/dist/host/agents/templates/brief.md +1 -1
- package/dist/host/agents/transports/claude-cli.md +3 -3
- package/dist/host/agents/transports/codex.md +3 -8
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +7 -10
- package/dist/host/claude/agents/analyst.md +3 -3
- package/dist/host/claude/agents/astra.md +3 -3
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +1 -1
- package/dist/host/claude/agents/distiller.md +1 -1
- package/dist/host/claude/agents/grok.md +1 -1
- package/dist/host/claude/agents/opus.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +57 -90
- package/dist/host/claude/agents/researcher.md +1 -1
- package/dist/host/claude/agents/reviewer.md +4 -0
- package/dist/host/claude/agents/scout.md +1 -1
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +14 -21
- package/dist/host/claude/rules/architecture.md +34 -44
- package/dist/host/claude/rules/browser.md +3 -4
- package/dist/host/claude/rules/campaign.md +55 -0
- package/dist/host/claude/rules/documentation.md +26 -11
- package/dist/host/claude/rules/names.md +9 -4
- package/dist/host/claude/rules/patterns.md +6 -7
- package/dist/host/claude/rules/portability.md +2 -3
- package/dist/host/claude/rules/quality.md +4 -5
- package/dist/host/claude/rules/styles.md +2 -2
- package/dist/host/claude/rules/tests.md +57 -114
- package/dist/host/claude/rules/typescript.md +8 -10
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +3 -3
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +9 -3
- package/dist/host/codex/agents/astra.toml +3 -4
- package/dist/host/codex/agents/builder.toml +1 -1
- package/dist/host/codex/agents/opus.toml +3 -4
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/verifier.toml +1 -1
- package/dist/host/codex/config.toml +3 -3
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +405 -16
- package/dist/host/cursor/rules/orchestration.mdc +1 -0
- package/dist/host/dotfiles/oxlintrc.json +141 -5
- package/dist/host/guides/agent.md +111 -134
- package/dist/host/guides/capture.md +801 -0
- package/dist/host/guides/probe.md +27 -8
- package/dist/host/guides/scaffold.md +152 -19
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +199 -242
- package/dist/host/tests/config.test.ts +1558 -219
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +1389 -39
- package/dist/src/core/index.cjs +239 -87
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -39
- package/dist/src/core/index.d.ts +123 -39
- package/dist/src/core/index.js +234 -86
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +20 -27
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +21 -28
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
- package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
- package/dist/host/guides/supervisor.md +0 -4890
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
// P2: Required items are checked strictly; extra items are ignored before their shape is read.
|
|
3
3
|
|
|
4
4
|
import type { InlineConfig, UserConfig } from 'vite'
|
|
5
|
-
import type {
|
|
6
|
-
PolicyComment,
|
|
7
|
-
PolicyContext,
|
|
8
|
-
PolicyDiagnostic,
|
|
9
|
-
PolicyExpression,
|
|
10
|
-
PolicySourceCode,
|
|
11
|
-
} from '../configs/policy.js'
|
|
5
|
+
import type { PolicyComment, PolicyDiagnostic, PolicyExpression } from '../configs/policy.js'
|
|
12
6
|
import type { PolicyProject } from './setupPolicy.js'
|
|
13
7
|
import { spawnSync } from 'node:child_process'
|
|
14
8
|
import {
|
|
@@ -42,21 +36,28 @@ import policyPlugin, {
|
|
|
42
36
|
FUNCTION_RULE,
|
|
43
37
|
FUNCTION_SOURCE_FILES,
|
|
44
38
|
HIDDEN_RULE,
|
|
39
|
+
LET_RULE,
|
|
45
40
|
MOCKING_RULE,
|
|
41
|
+
MUTABLE_RULE,
|
|
46
42
|
NESTED_RULE,
|
|
47
43
|
PARSER_RULE,
|
|
48
44
|
PLUGIN_RULE,
|
|
49
45
|
POLICY_BANNED_TERMS,
|
|
50
46
|
POLICY_ENDING_GLOBS,
|
|
51
47
|
POLICY_JUDGED_TERMS,
|
|
48
|
+
POLICY_NESTED_GLOBS,
|
|
52
49
|
POLICY_PLACEMENT_GLOBS,
|
|
50
|
+
POLICY_SCRIPT_GLOBS,
|
|
53
51
|
POLICY_VOICE_STOPWORDS,
|
|
54
52
|
PRIVACY_RULE,
|
|
53
|
+
SCRIPT_RULE,
|
|
55
54
|
TERM_RULE,
|
|
56
55
|
TYPE_RULE,
|
|
57
56
|
VOICE_RULE,
|
|
57
|
+
VUE_IMPORT_RULE,
|
|
58
58
|
blankPolicyText,
|
|
59
59
|
commentToPolicyParagraph,
|
|
60
|
+
computePolicyTarget,
|
|
60
61
|
isPolicyVoiced,
|
|
61
62
|
nodeToPolicyOuter,
|
|
62
63
|
paragraphToPolicyOpener,
|
|
@@ -82,16 +83,42 @@ import {
|
|
|
82
83
|
SHEET_POLICY_BARREL_PATTERN,
|
|
83
84
|
SHEET_POLICY_ORDER_PATTERN,
|
|
84
85
|
readImportDiagnostics,
|
|
86
|
+
readNativeDiagnostics,
|
|
87
|
+
createPolicyComment,
|
|
88
|
+
createPolicyContext,
|
|
89
|
+
createPolicyDeclarator,
|
|
90
|
+
createPolicyExport,
|
|
85
91
|
createPolicyLazyCases,
|
|
92
|
+
createPolicyRefused,
|
|
86
93
|
createPolicyScratch,
|
|
94
|
+
createPolicySource,
|
|
87
95
|
createPolicySparse,
|
|
88
96
|
inspectPolicyConfiguration,
|
|
89
97
|
inspectPolicyWiring,
|
|
90
98
|
normalizePolicyFilename,
|
|
91
99
|
POLICY_EXTRACTOR,
|
|
100
|
+
POLICY_IMPORT_CASES,
|
|
92
101
|
POLICY_LAZY_SHAPES,
|
|
102
|
+
POLICY_NAME_CASES,
|
|
93
103
|
POLICY_PUBLISHES,
|
|
94
104
|
POLICY_ROOT,
|
|
105
|
+
POLICY_WIRING_SETTINGS,
|
|
106
|
+
POLICY_VUE_COMPOSABLE_SOURCE,
|
|
107
|
+
POLICY_VUE_RELATIVE_SOURCES,
|
|
108
|
+
POLICY_VUE_TARGET_CASES,
|
|
109
|
+
POLICY_VUE_FACES,
|
|
110
|
+
POLICY_VUE_UNCLASSIFIED_FILES,
|
|
111
|
+
POLICY_VUE_IMPORT_FORMS,
|
|
112
|
+
POLICY_VUE_POPULATIONS,
|
|
113
|
+
POLICY_VUE_IMPORT_CASES,
|
|
114
|
+
POLICY_VUE_BOUNDARY_CASES,
|
|
115
|
+
POLICY_VUE_COMPUTED_CASES,
|
|
116
|
+
POLICY_VUE_COMPUTED_FORMS,
|
|
117
|
+
POLICY_VUE_PARSER_ROWS,
|
|
118
|
+
POLICY_VUE_PARSER_CASES,
|
|
119
|
+
POLICY_VUE_PARSED_CASES,
|
|
120
|
+
POLICY_VUE_ORIGINAL_RELATIVES,
|
|
121
|
+
POLICY_VUE_DOMAIN_CASES,
|
|
95
122
|
} from './setupPolicy.js'
|
|
96
123
|
import { describe, expect, it } from 'vitest'
|
|
97
124
|
|
|
@@ -225,7 +252,7 @@ describe('selected faces', () => {
|
|
|
225
252
|
const sheets = collectSheets(POLICY_ROOT)
|
|
226
253
|
const frameworks = collectFrameworks(POLICY_ROOT)
|
|
227
254
|
const wrappers = collectFaceWrappers(POLICY_ROOT)
|
|
228
|
-
const
|
|
255
|
+
const configurations: Array<{
|
|
229
256
|
readonly axis: string
|
|
230
257
|
readonly face: string
|
|
231
258
|
readonly wrapper: string
|
|
@@ -260,10 +287,10 @@ describe('selected faces', () => {
|
|
|
260
287
|
expect(
|
|
261
288
|
Object.getOwnPropertyDescriptor(tsconfig.compilerOptions.paths, `@${axis}/${face}`)?.value,
|
|
262
289
|
).toEqual([`./${axis}/${face}/index.ts`])
|
|
263
|
-
|
|
290
|
+
configurations.push({ axis, face, wrapper, project })
|
|
264
291
|
}
|
|
265
|
-
for (const { face, wrapper, project } of
|
|
266
|
-
sheets.includes(
|
|
292
|
+
for (const { face, wrapper, project } of configurations.filter((faceConfiguration) =>
|
|
293
|
+
sheets.includes(faceConfiguration.face),
|
|
267
294
|
)) {
|
|
268
295
|
expect(readFileSync(resolve(POLICY_ROOT, wrapper), 'utf8')).toContain('sheetProject(')
|
|
269
296
|
expect(inspectSheetConfiguration(project)).toEqual([])
|
|
@@ -277,16 +304,18 @@ describe('selected faces', () => {
|
|
|
277
304
|
)
|
|
278
305
|
expect(readConfigScript(scripts, 'build:src')).toContain(`npm run build:src:${face}`)
|
|
279
306
|
}
|
|
280
|
-
for (const { project } of
|
|
281
|
-
frameworks.includes(`${
|
|
307
|
+
for (const { project } of configurations.filter((faceConfiguration) =>
|
|
308
|
+
frameworks.includes(`${faceConfiguration.axis}/${faceConfiguration.face}`),
|
|
282
309
|
)) {
|
|
283
310
|
expect(project.optimizeDeps?.include).toContain('vue')
|
|
284
311
|
expect(project.test?.setupFiles).toEqual(
|
|
285
312
|
expect.arrayContaining(['./tests/setup.ts', './tests/setupBrowser.ts']),
|
|
286
313
|
)
|
|
287
314
|
}
|
|
288
|
-
for (const { wrapper } of
|
|
289
|
-
(
|
|
315
|
+
for (const { wrapper } of configurations.filter(
|
|
316
|
+
(faceConfiguration) =>
|
|
317
|
+
faceConfiguration.axis === 'src' &&
|
|
318
|
+
frameworks.includes(`${faceConfiguration.axis}/${faceConfiguration.face}`),
|
|
290
319
|
)) {
|
|
291
320
|
const text = readFileSync(resolve(POLICY_ROOT, wrapper), 'utf8')
|
|
292
321
|
expect(text).toContain('rewriteCoreSpecifier')
|
|
@@ -785,7 +814,7 @@ describe('root configuration', () => {
|
|
|
785
814
|
[
|
|
786
815
|
'probe',
|
|
787
816
|
{
|
|
788
|
-
benchmark: ['tmp/probes/**/*.
|
|
817
|
+
benchmark: ['tmp/probes/**/*.bench.ts', 'tests/**/*.bench.ts'],
|
|
789
818
|
include: 'tmp/probes/**/*.test.ts',
|
|
790
819
|
parallel: false,
|
|
791
820
|
pool: 'threads',
|
|
@@ -975,8 +1004,8 @@ describe('root configuration', () => {
|
|
|
975
1004
|
sentinel: true,
|
|
976
1005
|
}
|
|
977
1006
|
const test = { name: { label: 'control' }, include: ['tests/control.test.ts'] }
|
|
978
|
-
const
|
|
979
|
-
const
|
|
1007
|
+
const ignoreInput = Object.defineProperty(() => ({ test }), 'name', { value: 'ignoring' })
|
|
1008
|
+
const spreadInput = Object.defineProperty((record: object) => ({ ...record, test }), 'name', {
|
|
980
1009
|
value: 'spreading',
|
|
981
1010
|
})
|
|
982
1011
|
const wrappers = projects.filter((entry) => typeof entry === 'string')
|
|
@@ -1008,8 +1037,8 @@ describe('root configuration', () => {
|
|
|
1008
1037
|
).toThrow('expected')
|
|
1009
1038
|
}
|
|
1010
1039
|
const factories = projects.filter((entry) => typeof entry !== 'string')
|
|
1011
|
-
const entries: readonly unknown[] = [...factories,
|
|
1012
|
-
const
|
|
1040
|
+
const entries: readonly unknown[] = [...factories, ignoreInput, spreadInput, { test }]
|
|
1041
|
+
const outcomes = entries.map((entry) => {
|
|
1013
1042
|
if (typeof entry !== 'function') {
|
|
1014
1043
|
return { name: undefined, callable: false, mode: undefined, leaked: [] }
|
|
1015
1044
|
}
|
|
@@ -1027,13 +1056,13 @@ describe('root configuration', () => {
|
|
|
1027
1056
|
}
|
|
1028
1057
|
})
|
|
1029
1058
|
const forwarded = { callable: true, mode: 'sentinel-mode', leaked: [] }
|
|
1030
|
-
expect(
|
|
1059
|
+
expect(outcomes.slice(0, factories.length)).toStrictEqual(
|
|
1031
1060
|
factories.map((entry) => ({
|
|
1032
1061
|
name: typeof entry === 'function' ? entry.name : undefined,
|
|
1033
1062
|
...forwarded,
|
|
1034
1063
|
})),
|
|
1035
1064
|
)
|
|
1036
|
-
expect(
|
|
1065
|
+
expect(outcomes.slice(factories.length)).toStrictEqual([
|
|
1037
1066
|
{ name: 'ignoring', callable: true, mode: undefined, leaked: [] },
|
|
1038
1067
|
{
|
|
1039
1068
|
name: 'spreading',
|
|
@@ -1043,8 +1072,8 @@ describe('root configuration', () => {
|
|
|
1043
1072
|
},
|
|
1044
1073
|
{ name: undefined, callable: false, mode: undefined, leaked: [] },
|
|
1045
1074
|
])
|
|
1046
|
-
for (const
|
|
1047
|
-
expect({ ...
|
|
1075
|
+
for (const outcome of outcomes.slice(factories.length)) {
|
|
1076
|
+
expect({ ...outcome, name: undefined }).not.toStrictEqual({ name: undefined, ...forwarded })
|
|
1048
1077
|
}
|
|
1049
1078
|
|
|
1050
1079
|
// A record that carries no string `mode` is refused rather than forwarded as a project
|
|
@@ -1282,6 +1311,85 @@ describe('root configuration', () => {
|
|
|
1282
1311
|
).toThrow(/expected/u)
|
|
1283
1312
|
})
|
|
1284
1313
|
|
|
1314
|
+
// A target with a server application builds it to one `main.cjs`, so a
|
|
1315
|
+
// single-executable embed has one file to take. The real wrapper's configuration
|
|
1316
|
+
// builds a scratch entry that dynamically imports a scratch module, once as the
|
|
1317
|
+
// wrapper declares it and once with splitting forced on. The second build is the
|
|
1318
|
+
// control: it shows the single chunk comes from the wrapper's `codeSplitting` option
|
|
1319
|
+
// and not from the entry's shape. Evaluation order of the inlined module is proved
|
|
1320
|
+
// once, by executing the emitted file, in the template proof that stages a server
|
|
1321
|
+
// root. A workspace without an `app/server` has no wrapper to build, so this case
|
|
1322
|
+
// reports nothing about the server application of a target that has one: the
|
|
1323
|
+
// assertion below only classifies the absence, and each target with a server
|
|
1324
|
+
// application establishes this proof when its own configuration test runs.
|
|
1325
|
+
it('builds the application server wrapper to one entry and splits it under the control', async () => {
|
|
1326
|
+
const wrapper = resolve(POLICY_ROOT, 'configs/app/vite.server.config.ts')
|
|
1327
|
+
const present = existsSync(wrapper)
|
|
1328
|
+
expect(present || !existsSync(resolve(POLICY_ROOT, 'app/server'))).toBe(true)
|
|
1329
|
+
if (!present) return
|
|
1330
|
+
const scratch = createPolicyScratch({
|
|
1331
|
+
parent: resolve(POLICY_ROOT, 'app/server'),
|
|
1332
|
+
prefix: 'config-server-entry-',
|
|
1333
|
+
})
|
|
1334
|
+
try {
|
|
1335
|
+
scratch.write(
|
|
1336
|
+
'index.ts',
|
|
1337
|
+
"process.stdout.write('entry\\n')\nvoid import('./lazy.js').then(({ lazy }) => process.stdout.write(lazy()))\nprocess.stdout.write('after\\n')\n",
|
|
1338
|
+
)
|
|
1339
|
+
scratch.write(
|
|
1340
|
+
'lazy.ts',
|
|
1341
|
+
"process.stdout.write('lazy\\n')\nexport function lazy(): string {\n\treturn String(process.argv.length)\n}\n",
|
|
1342
|
+
)
|
|
1343
|
+
const rosters: Array<readonly string[]> = []
|
|
1344
|
+
for (const chunked of [false, true]) {
|
|
1345
|
+
const loaded = await loadConfigFromFile(
|
|
1346
|
+
{ command: 'build', mode: 'production', isSsrBuild: false, isPreview: false },
|
|
1347
|
+
wrapper,
|
|
1348
|
+
POLICY_ROOT,
|
|
1349
|
+
'silent',
|
|
1350
|
+
)
|
|
1351
|
+
if (loaded === null) throw new Error('The server wrapper did not load')
|
|
1352
|
+
const rolldown = loaded.config.build?.rolldownOptions
|
|
1353
|
+
const output: unknown = rolldown?.output
|
|
1354
|
+
if (typeof output !== 'object' || output === null || Array.isArray(output)) {
|
|
1355
|
+
throw new Error('The server wrapper declares no output options')
|
|
1356
|
+
}
|
|
1357
|
+
expect(Object.getOwnPropertyDescriptor(output, 'codeSplitting')?.value).toBe(false)
|
|
1358
|
+
const built = await build({
|
|
1359
|
+
...loaded.config,
|
|
1360
|
+
root: POLICY_ROOT,
|
|
1361
|
+
configFile: false,
|
|
1362
|
+
logLevel: 'silent',
|
|
1363
|
+
build: {
|
|
1364
|
+
...loaded.config.build,
|
|
1365
|
+
write: false,
|
|
1366
|
+
lib: {
|
|
1367
|
+
entry: resolve(scratch.path, 'index.ts'),
|
|
1368
|
+
formats: ['cjs'],
|
|
1369
|
+
fileName: () => 'main.cjs',
|
|
1370
|
+
},
|
|
1371
|
+
rolldownOptions: chunked
|
|
1372
|
+
? { ...rolldown, output: { codeSplitting: true } }
|
|
1373
|
+
: { ...rolldown },
|
|
1374
|
+
},
|
|
1375
|
+
})
|
|
1376
|
+
// A library build returns one result per format, an array even for one format.
|
|
1377
|
+
const results = Array.isArray(built) ? built : [built]
|
|
1378
|
+
expect(results).toHaveLength(1)
|
|
1379
|
+
for (const result of results) {
|
|
1380
|
+
if (!('output' in result)) throw new Error('The server build returned no output')
|
|
1381
|
+
rosters.push(result.output.map(({ fileName }) => fileName).toSorted())
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const [single, split] = rosters
|
|
1385
|
+
expect(single).toStrictEqual(['main.cjs'])
|
|
1386
|
+
expect(split).toContain('main.cjs')
|
|
1387
|
+
expect(split?.length).toBeGreaterThan(1)
|
|
1388
|
+
} finally {
|
|
1389
|
+
scratch.destroy()
|
|
1390
|
+
}
|
|
1391
|
+
})
|
|
1392
|
+
|
|
1285
1393
|
it('registers proof scripts in the correct gate', () => {
|
|
1286
1394
|
const manifest: unknown = JSON.parse(readFileSync(resolve(POLICY_ROOT, 'package.json'), 'utf8'))
|
|
1287
1395
|
if (typeof manifest !== 'object' || manifest === null) {
|
|
@@ -1549,6 +1657,49 @@ describe('root configuration', () => {
|
|
|
1549
1657
|
expect(inspectPolicyConfiguration(controlled)).toEqual([
|
|
1550
1658
|
'overrides must not configure policy/no-mocking',
|
|
1551
1659
|
])
|
|
1660
|
+
// The skill scripts sit under .agents, so an ignore pattern that reaches the folder defeats the
|
|
1661
|
+
// script rules as an ignore of a source root defeats the placement rules.
|
|
1662
|
+
const ignored = structuredClone(parsed)
|
|
1663
|
+
Object.defineProperty(ignored, 'ignorePatterns', {
|
|
1664
|
+
value: ['.orkestrel/', '.agents/skills/'],
|
|
1665
|
+
enumerable: true,
|
|
1666
|
+
configurable: true,
|
|
1667
|
+
writable: true,
|
|
1668
|
+
})
|
|
1669
|
+
expect(inspectPolicyConfiguration(ignored)).toEqual([
|
|
1670
|
+
'ignorePatterns must not reach .agents/skills/',
|
|
1671
|
+
])
|
|
1672
|
+
// An entry without an inner slash matches at any depth, so a glob or a directory name the
|
|
1673
|
+
// overrides route through silences guarded files while its first segment names no root.
|
|
1674
|
+
for (const pattern of ['*.ts', 'scripts', 'scripts/', 'policy.ts']) {
|
|
1675
|
+
expect(
|
|
1676
|
+
inspectPolicyConfiguration({ ...readConfigRecord(parsed), ignorePatterns: [pattern] }),
|
|
1677
|
+
).toEqual([`ignorePatterns must not reach ${pattern}`])
|
|
1678
|
+
}
|
|
1679
|
+
for (const patterns of [['.orkestrel/'], ['.orkestrel/', '!src/keep.ts'], ['./scripts']]) {
|
|
1680
|
+
expect(
|
|
1681
|
+
inspectPolicyConfiguration({ ...readConfigRecord(parsed), ignorePatterns: patterns }),
|
|
1682
|
+
).toEqual([])
|
|
1683
|
+
}
|
|
1684
|
+
})
|
|
1685
|
+
|
|
1686
|
+
it('refuses a wiring rule whose options weaken it beneath an error severity', () => {
|
|
1687
|
+
const parsed: unknown = JSON.parse(readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
1688
|
+
const rules = readConfigRecord(readConfigRecord(parsed).rules)
|
|
1689
|
+
for (const [rule, setting] of [
|
|
1690
|
+
['typescript/explicit-member-accessibility', ['error', { accessibility: 'off' }]],
|
|
1691
|
+
['typescript/parameter-properties', ['error', { allow: ['private readonly'] }]],
|
|
1692
|
+
['no-var', ['error', { ignore: true }]],
|
|
1693
|
+
] as const) {
|
|
1694
|
+
expect(
|
|
1695
|
+
inspectPolicyConfiguration({
|
|
1696
|
+
...readConfigRecord(parsed),
|
|
1697
|
+
rules: { ...rules, [rule]: setting },
|
|
1698
|
+
}),
|
|
1699
|
+
).toEqual([
|
|
1700
|
+
`${rule} must have the top-level setting ${JSON.stringify(POLICY_WIRING_SETTINGS[rule])}`,
|
|
1701
|
+
])
|
|
1702
|
+
}
|
|
1552
1703
|
})
|
|
1553
1704
|
|
|
1554
1705
|
it('omits the audit-confirmed dead policy type exports', () => {
|
|
@@ -1562,6 +1713,35 @@ describe('policy plugin', () => {
|
|
|
1562
1713
|
RuleTester.it = it
|
|
1563
1714
|
|
|
1564
1715
|
const tester = new RuleTester({ languageOptions: { parserOptions: { lang: 'ts' } } })
|
|
1716
|
+
tester.run('no-cross-vue-face', VUE_IMPORT_RULE, {
|
|
1717
|
+
valid: [...POLICY_VUE_IMPORT_CASES, ...POLICY_VUE_BOUNDARY_CASES, ...POLICY_VUE_COMPUTED_CASES]
|
|
1718
|
+
.filter((source) => source.path.endsWith('.ts') && !source.boundary)
|
|
1719
|
+
.map((source) => ({
|
|
1720
|
+
name: `accepts the native relative boundary in ${source.path}`,
|
|
1721
|
+
filename: resolve(POLICY_ROOT, source.path),
|
|
1722
|
+
cwd: POLICY_ROOT,
|
|
1723
|
+
code: source.content,
|
|
1724
|
+
})),
|
|
1725
|
+
invalid: [
|
|
1726
|
+
...POLICY_VUE_IMPORT_CASES,
|
|
1727
|
+
...POLICY_VUE_BOUNDARY_CASES,
|
|
1728
|
+
...POLICY_VUE_UNCLASSIFIED_FILES.map((path) => ({
|
|
1729
|
+
path,
|
|
1730
|
+
content: "import './entry.js'\ndebugger\n",
|
|
1731
|
+
boundary: true,
|
|
1732
|
+
restricted: false,
|
|
1733
|
+
})),
|
|
1734
|
+
]
|
|
1735
|
+
.filter((source) => source.path.endsWith('.ts') && source.boundary)
|
|
1736
|
+
.map((source) => ({
|
|
1737
|
+
name: `rejects the native relative boundary in ${source.path} [membership: literal relative import or export outside permitted Vue destinations]`,
|
|
1738
|
+
filename: resolve(POLICY_ROOT, source.path),
|
|
1739
|
+
cwd: POLICY_ROOT,
|
|
1740
|
+
code: source.content,
|
|
1741
|
+
errors: [{ messageId: 'vue' }],
|
|
1742
|
+
})),
|
|
1743
|
+
})
|
|
1744
|
+
|
|
1565
1745
|
tester.run('no-mocking', MOCKING_RULE, {
|
|
1566
1746
|
valid: [
|
|
1567
1747
|
{ name: 'accepts recorders', code: 'createRecorder()' },
|
|
@@ -1634,6 +1814,80 @@ describe('policy plugin', () => {
|
|
|
1634
1814
|
],
|
|
1635
1815
|
})
|
|
1636
1816
|
|
|
1817
|
+
tester.run('no-let', LET_RULE, {
|
|
1818
|
+
valid: [
|
|
1819
|
+
{ name: 'leaves an unwritten let to prefer-const', code: 'let value = 1\nread(value)' },
|
|
1820
|
+
{
|
|
1821
|
+
name: 'leaves an unwritten for-of head to prefer-const',
|
|
1822
|
+
code: 'for (let entry of entries) read(entry)',
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
name: 'leaves an unwritten for-in head to prefer-const',
|
|
1826
|
+
code: 'for (let key in record) read(key)',
|
|
1827
|
+
},
|
|
1828
|
+
{ name: 'accepts const', code: 'const value = 1\nread(value)' },
|
|
1829
|
+
{ name: 'leaves var to no-var', code: 'var value\nvalue = 1' },
|
|
1830
|
+
{ name: 'accepts an ambient let', code: 'declare let value: number' },
|
|
1831
|
+
{
|
|
1832
|
+
name: 'accepts a let in a global augmentation',
|
|
1833
|
+
code: 'declare global { let ambient: number }',
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
name: 'accepts an exported let in a declared module',
|
|
1837
|
+
code: "declare module 'm' { export let ambient: string }",
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
name: 'accepts a let in a declared namespace',
|
|
1841
|
+
code: 'declare namespace Ambient { let value: number }',
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
name: 'accepts a let in a namespace nested in a declared module',
|
|
1845
|
+
code: "declare module 'm' { namespace Inner { let value: number } }",
|
|
1846
|
+
},
|
|
1847
|
+
],
|
|
1848
|
+
invalid: [
|
|
1849
|
+
...[
|
|
1850
|
+
{ form: 'assignment', code: 'let count = 0\ncount = 1' },
|
|
1851
|
+
{ form: 'compound assignment', code: 'let count = 0\ncount += 1' },
|
|
1852
|
+
{ form: 'logical assignment', code: 'let count: number | undefined = 0\ncount ??= 1' },
|
|
1853
|
+
{ form: 'update', code: 'let count = 0\ncount++' },
|
|
1854
|
+
{ form: 'counter loop', code: 'for (let index = 0; index < 3; index++) read(index)' },
|
|
1855
|
+
{ form: 'array destructuring target', code: 'let [first] = [1]\n;[first] = [2]' },
|
|
1856
|
+
{ form: 'object destructuring target', code: 'let { a } = record\n;({ a } = other)' },
|
|
1857
|
+
{
|
|
1858
|
+
form: 'for-of head written in the body',
|
|
1859
|
+
code: 'for (let entry of entries) { entry = 1 }',
|
|
1860
|
+
},
|
|
1861
|
+
{ form: 'closure write', code: 'let ready = false\nlisten(() => { ready = true })' },
|
|
1862
|
+
{ form: 'later declarator', code: 'let a = 1, b = 2\nb = 3\nread(a, b)' },
|
|
1863
|
+
{ form: 'declaration without a value', code: 'let value\nread(value)' },
|
|
1864
|
+
{ form: 'value assigned later', code: 'let value: number\nvalue = 1\nread(value)' },
|
|
1865
|
+
].map(({ form, code }) => ({
|
|
1866
|
+
name: `rejects a let by ${form} [membership: let that prefer-const leaves]`,
|
|
1867
|
+
code,
|
|
1868
|
+
errors: [{ messageId: 'let' }],
|
|
1869
|
+
})),
|
|
1870
|
+
...[
|
|
1871
|
+
{
|
|
1872
|
+
form: 'a bare let beside a global augmentation',
|
|
1873
|
+
code: 'declare global { let ambient: number }\nlet value\nread(value)',
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
form: 'a reassigned let beside a declared module',
|
|
1877
|
+
code: "declare module 'm' { export let ambient: string }\nlet count = 0\ncount++",
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
form: 'a bare let in a namespace that is not declared',
|
|
1881
|
+
code: 'namespace Local { let value: number }',
|
|
1882
|
+
},
|
|
1883
|
+
].map(({ form, code }) => ({
|
|
1884
|
+
name: `rejects ${form} [membership: let outside an ambient block]`,
|
|
1885
|
+
code,
|
|
1886
|
+
errors: [{ messageId: 'let' }],
|
|
1887
|
+
})),
|
|
1888
|
+
],
|
|
1889
|
+
})
|
|
1890
|
+
|
|
1637
1891
|
tester.run('no-nested-functions', NESTED_RULE, {
|
|
1638
1892
|
valid: [
|
|
1639
1893
|
{
|
|
@@ -1931,6 +2185,12 @@ createSomething({
|
|
|
1931
2185
|
|
|
1932
2186
|
tester.run('no-misplaced-function', FUNCTION_RULE, {
|
|
1933
2187
|
valid: [
|
|
2188
|
+
...POLICY_VUE_DOMAIN_CASES.filter((source) => !source.misplaced).map((source) => ({
|
|
2189
|
+
name: `accepts Vue function placement in ${source.path}`,
|
|
2190
|
+
filename: resolve(POLICY_ROOT, source.path),
|
|
2191
|
+
cwd: POLICY_ROOT,
|
|
2192
|
+
code: source.content,
|
|
2193
|
+
})),
|
|
1934
2194
|
{
|
|
1935
2195
|
name: 'accepts a function in a function-kind file',
|
|
1936
2196
|
filename: 'src/worker/helpers.ts',
|
|
@@ -1979,6 +2239,13 @@ createSomething({
|
|
|
1979
2239
|
code: 'export function runTask(): void {}',
|
|
1980
2240
|
errors: [{ messageId: 'function' }],
|
|
1981
2241
|
},
|
|
2242
|
+
...POLICY_VUE_DOMAIN_CASES.filter((source) => source.misplaced).map((source) => ({
|
|
2243
|
+
name: `rejects Vue function placement in ${source.path} [membership: modules outside the exact direct function register]`,
|
|
2244
|
+
filename: resolve(POLICY_ROOT, source.path),
|
|
2245
|
+
cwd: POLICY_ROOT,
|
|
2246
|
+
code: source.content,
|
|
2247
|
+
errors: [{ messageId: 'function' }],
|
|
2248
|
+
})),
|
|
1982
2249
|
{
|
|
1983
2250
|
name: 'rejects a property-held arrow in a route table [membership: module function syntax whose file is absent from the function register]',
|
|
1984
2251
|
filename: 'src/worker/routes.ts',
|
|
@@ -2281,6 +2548,12 @@ createSomething({
|
|
|
2281
2548
|
|
|
2282
2549
|
tester.run('no-malformed-domain', DOMAIN_RULE, {
|
|
2283
2550
|
valid: [
|
|
2551
|
+
...POLICY_VUE_DOMAIN_CASES.filter((source) => !source.malformed).map((source) => ({
|
|
2552
|
+
name: `accepts Vue domain shape in ${source.path}`,
|
|
2553
|
+
filename: resolve(POLICY_ROOT, source.path),
|
|
2554
|
+
cwd: POLICY_ROOT,
|
|
2555
|
+
code: source.content,
|
|
2556
|
+
})),
|
|
2284
2557
|
{
|
|
2285
2558
|
name: 'accepts a registered function module carrying imports and one named export',
|
|
2286
2559
|
filename: 'app/browser/composables/useTheme.ts',
|
|
@@ -2306,6 +2579,13 @@ createSomething({
|
|
|
2306
2579
|
code: 'export function useMode(): void {}',
|
|
2307
2580
|
errors: [{ messageId: 'module' }],
|
|
2308
2581
|
},
|
|
2582
|
+
...POLICY_VUE_DOMAIN_CASES.filter((source) => source.malformed).map((source) => ({
|
|
2583
|
+
name: `rejects Vue domain shape in ${source.path} [membership: direct camelCase modules in the Vue function domain]`,
|
|
2584
|
+
filename: resolve(POLICY_ROOT, source.path),
|
|
2585
|
+
cwd: POLICY_ROOT,
|
|
2586
|
+
code: source.content,
|
|
2587
|
+
errors: [{ messageId: 'module' }],
|
|
2588
|
+
})),
|
|
2309
2589
|
{
|
|
2310
2590
|
name: 'rejects a hidden domain function [membership: direct camelCase modules in a registered function-domain folder]',
|
|
2311
2591
|
filename: 'app/browser/composables/useTheme.ts',
|
|
@@ -2367,6 +2647,253 @@ createSomething({
|
|
|
2367
2647
|
],
|
|
2368
2648
|
})
|
|
2369
2649
|
|
|
2650
|
+
const SCRIPT_GUARD = [
|
|
2651
|
+
'if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {',
|
|
2652
|
+
'\tprocess.exitCode = main()',
|
|
2653
|
+
'}',
|
|
2654
|
+
].join('\n')
|
|
2655
|
+
|
|
2656
|
+
tester.run('no-malformed-script', SCRIPT_RULE, {
|
|
2657
|
+
valid: [
|
|
2658
|
+
{
|
|
2659
|
+
name: 'accepts imports, documented exports, and the one guarded entry',
|
|
2660
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2661
|
+
code: [
|
|
2662
|
+
"import { realpathSync } from 'node:fs'",
|
|
2663
|
+
'',
|
|
2664
|
+
'/** Reads the one value. */',
|
|
2665
|
+
'export function read(): number {',
|
|
2666
|
+
'\treturn 1',
|
|
2667
|
+
'}',
|
|
2668
|
+
'',
|
|
2669
|
+
'/** Runs the script. */',
|
|
2670
|
+
'export function main(): number {',
|
|
2671
|
+
'\treturn read()',
|
|
2672
|
+
'}',
|
|
2673
|
+
'',
|
|
2674
|
+
SCRIPT_GUARD,
|
|
2675
|
+
].join('\n'),
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
name: 'accepts a documented exported type, interface, and constant',
|
|
2679
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2680
|
+
code: [
|
|
2681
|
+
'/** Names the one value. */',
|
|
2682
|
+
'export type Value = number',
|
|
2683
|
+
'',
|
|
2684
|
+
'/** Describes the one record. */',
|
|
2685
|
+
'export interface Entry {',
|
|
2686
|
+
'\treadonly value: Value',
|
|
2687
|
+
'}',
|
|
2688
|
+
'',
|
|
2689
|
+
'/** Holds the one constant. */',
|
|
2690
|
+
'export const VALUE = 1',
|
|
2691
|
+
].join('\n'),
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
name: 'accepts a guard the formatter wrapped over lines',
|
|
2695
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2696
|
+
code: [
|
|
2697
|
+
'if (',
|
|
2698
|
+
'\tprocess.argv[1] !== undefined &&',
|
|
2699
|
+
'\trealpathSync(process.argv[1]) === import.meta.filename',
|
|
2700
|
+
') {',
|
|
2701
|
+
'\tmain()',
|
|
2702
|
+
'}',
|
|
2703
|
+
].join('\n'),
|
|
2704
|
+
},
|
|
2705
|
+
{
|
|
2706
|
+
name: 'accepts a doc block separated from its export by a blank line',
|
|
2707
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2708
|
+
code: ['/** Holds the one constant. */', '', 'export const VALUE = 1'].join('\n'),
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
name: 'accepts a module outside the skill scripts',
|
|
2712
|
+
filename: 'src/worker/helpers.ts',
|
|
2713
|
+
code: 'function hidden(): void {}\nhidden()',
|
|
2714
|
+
},
|
|
2715
|
+
{
|
|
2716
|
+
name: 'accepts a file beside the skill scripts',
|
|
2717
|
+
filename: '.agents/skills/sample/references/tool.ts',
|
|
2718
|
+
code: 'function hidden(): void {}\nhidden()',
|
|
2719
|
+
},
|
|
2720
|
+
],
|
|
2721
|
+
invalid: [
|
|
2722
|
+
{
|
|
2723
|
+
name: 'rejects an unexported function [membership: top-level declarations of a skill script]',
|
|
2724
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2725
|
+
code: 'function read(): number { return 1 }',
|
|
2726
|
+
errors: [{ messageId: 'hidden' }],
|
|
2727
|
+
},
|
|
2728
|
+
{
|
|
2729
|
+
name: 'rejects an unexported constant [membership: top-level declarations of a skill script]',
|
|
2730
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2731
|
+
code: 'const VALUE = 1',
|
|
2732
|
+
errors: [{ messageId: 'hidden' }],
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
name: 'rejects an unexported interface even with a doc block [membership: top-level declarations of a skill script]',
|
|
2736
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2737
|
+
code: [
|
|
2738
|
+
'/** Describes the one record. */',
|
|
2739
|
+
'interface Value { readonly value: number }',
|
|
2740
|
+
].join('\n'),
|
|
2741
|
+
errors: [{ messageId: 'hidden' }],
|
|
2742
|
+
},
|
|
2743
|
+
{
|
|
2744
|
+
name: 'rejects an export with no comment above it [membership: exported declarations of a skill script]',
|
|
2745
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2746
|
+
code: 'export function read(): number { return 1 }',
|
|
2747
|
+
errors: [{ messageId: 'doc' }],
|
|
2748
|
+
},
|
|
2749
|
+
{
|
|
2750
|
+
name: 'rejects an export under a line comment [membership: exported declarations of a skill script]',
|
|
2751
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2752
|
+
code: ['// Reads the one value.', 'export function read(): number { return 1 }'].join('\n'),
|
|
2753
|
+
errors: [{ messageId: 'doc' }],
|
|
2754
|
+
},
|
|
2755
|
+
{
|
|
2756
|
+
name: 'rejects an export under a plain block comment [membership: exported declarations of a skill script]',
|
|
2757
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2758
|
+
code: ['/* Reads the one value. */', 'export const VALUE = 1'].join('\n'),
|
|
2759
|
+
errors: [{ messageId: 'doc' }],
|
|
2760
|
+
},
|
|
2761
|
+
{
|
|
2762
|
+
name: 'rejects a doc block separated from its export by code [membership: exported declarations of a skill script]',
|
|
2763
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2764
|
+
code: ['/** Holds the one constant. */', 'const FIRST = 1', 'export const SECOND = 2'].join(
|
|
2765
|
+
'\n',
|
|
2766
|
+
),
|
|
2767
|
+
errors: [{ messageId: 'hidden' }, { messageId: 'doc' }],
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
name: 'rejects a bare call [membership: top-level statements of a skill script]',
|
|
2771
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2772
|
+
code: 'main()',
|
|
2773
|
+
errors: [{ messageId: 'entry' }],
|
|
2774
|
+
},
|
|
2775
|
+
{
|
|
2776
|
+
name: 'rejects an entry under another condition [membership: top-level statements of a skill script]',
|
|
2777
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2778
|
+
code: ['if (process.argv[1] !== undefined) {', '\tmain()', '}'].join('\n'),
|
|
2779
|
+
errors: [{ messageId: 'entry' }],
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
name: 'rejects the guard when it carries an else branch [membership: top-level statements of a skill script]',
|
|
2783
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2784
|
+
code: [SCRIPT_GUARD.replace(/\}$/u, '} else {'), '\tmain()', '}'].join('\n'),
|
|
2785
|
+
errors: [{ messageId: 'entry' }],
|
|
2786
|
+
},
|
|
2787
|
+
{
|
|
2788
|
+
name: 'rejects a second guard [membership: top-level statements of a skill script]',
|
|
2789
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2790
|
+
code: [SCRIPT_GUARD, SCRIPT_GUARD].join('\n'),
|
|
2791
|
+
errors: [{ messageId: 'entry' }],
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
name: 'rejects an export list [membership: top-level statements of a skill script]',
|
|
2795
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2796
|
+
code: ['/** Holds the one constant. */', 'const VALUE = 1', 'export { VALUE }'].join('\n'),
|
|
2797
|
+
errors: [{ messageId: 'hidden' }, { messageId: 'entry' }],
|
|
2798
|
+
},
|
|
2799
|
+
{
|
|
2800
|
+
name: 'rejects a default export [membership: top-level statements of a skill script]',
|
|
2801
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2802
|
+
code: 'export default {}',
|
|
2803
|
+
errors: [{ messageId: 'entry' }],
|
|
2804
|
+
},
|
|
2805
|
+
],
|
|
2806
|
+
})
|
|
2807
|
+
|
|
2808
|
+
tester.run('no-mutable-property', MUTABLE_RULE, {
|
|
2809
|
+
valid: [
|
|
2810
|
+
{
|
|
2811
|
+
name: 'accepts a readonly interface property',
|
|
2812
|
+
filename: 'src/core/types.ts',
|
|
2813
|
+
code: 'export interface Value { readonly id: string }',
|
|
2814
|
+
},
|
|
2815
|
+
{
|
|
2816
|
+
name: 'accepts a readonly property of a type alias object',
|
|
2817
|
+
filename: 'src/core/types.ts',
|
|
2818
|
+
code: 'export type Value = { readonly id: string; readonly name?: string }',
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
name: 'accepts a method signature',
|
|
2822
|
+
filename: 'src/core/types.ts',
|
|
2823
|
+
code: 'export interface Value { read(): string }',
|
|
2824
|
+
},
|
|
2825
|
+
{
|
|
2826
|
+
name: 'accepts an inline parameter type',
|
|
2827
|
+
filename: 'src/core/helpers.ts',
|
|
2828
|
+
code: 'export function read(options: { id: string }): string { return options.id }',
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
name: 'accepts an inline parameter type of an interface method',
|
|
2832
|
+
filename: 'src/core/types.ts',
|
|
2833
|
+
code: 'export interface Value { read(options: { id: string }): string }',
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
name: 'accepts an inline parameter type of a function typed property',
|
|
2837
|
+
filename: 'src/core/types.ts',
|
|
2838
|
+
code: 'export interface Value { readonly read: (options: { id: string }) => string }',
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
name: 'accepts an object type annotating a binding',
|
|
2842
|
+
filename: 'src/core/constants.ts',
|
|
2843
|
+
code: "export const VALUE: { id: string } = { id: 'a' }",
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
name: 'accepts a mutable property of an ambient declaration file',
|
|
2847
|
+
filename: 'src/core/types.d.ts',
|
|
2848
|
+
code: 'export interface Value { id: string }',
|
|
2849
|
+
},
|
|
2850
|
+
],
|
|
2851
|
+
invalid: [
|
|
2852
|
+
{
|
|
2853
|
+
name: 'rejects a mutable interface property [membership: property signatures of an interface]',
|
|
2854
|
+
filename: 'src/core/types.ts',
|
|
2855
|
+
code: 'export interface Value { id: string }',
|
|
2856
|
+
errors: [{ messageId: 'mutable' }],
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
name: 'rejects a mutable optional interface property [membership: property signatures of an interface]',
|
|
2860
|
+
filename: 'src/core/types.ts',
|
|
2861
|
+
code: 'export interface Value { readonly id: string; name?: string }',
|
|
2862
|
+
errors: [{ messageId: 'mutable' }],
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
name: 'rejects a mutable property of a type alias object [membership: property signatures of a type alias object type]',
|
|
2866
|
+
filename: 'src/core/types.ts',
|
|
2867
|
+
code: 'export type Value = { id: string }',
|
|
2868
|
+
errors: [{ messageId: 'mutable' }],
|
|
2869
|
+
},
|
|
2870
|
+
{
|
|
2871
|
+
name: 'rejects a mutable property of a union member [membership: property signatures of a type alias object type]',
|
|
2872
|
+
filename: 'src/core/types.ts',
|
|
2873
|
+
code: 'export type Value = { readonly id: string } | { name: string }',
|
|
2874
|
+
errors: [{ messageId: 'mutable' }],
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
name: 'rejects a mutable property of a nested object type [membership: property signatures of a type alias object type]',
|
|
2878
|
+
filename: 'src/core/types.ts',
|
|
2879
|
+
code: 'export interface Value { readonly inner: { id: string } }',
|
|
2880
|
+
errors: [{ messageId: 'mutable' }],
|
|
2881
|
+
},
|
|
2882
|
+
{
|
|
2883
|
+
name: 'rejects a mutable property of a generic argument [membership: property signatures of a type alias object type]',
|
|
2884
|
+
filename: 'src/core/types.ts',
|
|
2885
|
+
code: 'export type Value = ReadonlyArray<{ id: string }>',
|
|
2886
|
+
errors: [{ messageId: 'mutable' }],
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
name: 'rejects a mutable function typed property [membership: property signatures of an interface]',
|
|
2890
|
+
filename: 'src/core/types.ts',
|
|
2891
|
+
code: 'export interface Value { read: () => string }',
|
|
2892
|
+
errors: [{ messageId: 'mutable' }],
|
|
2893
|
+
},
|
|
2894
|
+
],
|
|
2895
|
+
})
|
|
2896
|
+
|
|
2370
2897
|
tester.run('no-malformed-summary', VOICE_RULE, {
|
|
2371
2898
|
valid: [
|
|
2372
2899
|
{
|
|
@@ -2734,7 +3261,7 @@ createSomething({
|
|
|
2734
3261
|
range: [47, 63],
|
|
2735
3262
|
declaration: { type: 'FunctionDeclaration', range: [0, 0], id: name },
|
|
2736
3263
|
}
|
|
2737
|
-
const source
|
|
3264
|
+
const source = createPolicySource('')
|
|
2738
3265
|
|
|
2739
3266
|
it('refuses a hole in the declarators of a statement as the declarator it reads as', () => {
|
|
2740
3267
|
const declarator: PolicyExpression = {
|
|
@@ -2782,14 +3309,7 @@ createSomething({
|
|
|
2782
3309
|
|
|
2783
3310
|
it('refuses a hole in the body of a function-domain module as the statement it reads as', () => {
|
|
2784
3311
|
const reports: PolicyDiagnostic[] = []
|
|
2785
|
-
const context
|
|
2786
|
-
filename: '/ws/src/server/execution/run.ts',
|
|
2787
|
-
cwd: '/ws',
|
|
2788
|
-
sourceCode: source,
|
|
2789
|
-
report: (diagnostic) => {
|
|
2790
|
-
reports.push(diagnostic)
|
|
2791
|
-
},
|
|
2792
|
-
}
|
|
3312
|
+
const context = createPolicyContext('/ws/src/server/execution/run.ts', '/ws', reports)
|
|
2793
3313
|
const dense: PolicyExpression = { type: 'Program', range: [0, 0], body: [exported] }
|
|
2794
3314
|
const sparse: PolicyExpression = {
|
|
2795
3315
|
type: 'Program',
|
|
@@ -2855,12 +3375,12 @@ createSomething({
|
|
|
2855
3375
|
{ esmodule: false },
|
|
2856
3376
|
])
|
|
2857
3377
|
if (!Array.isArray(configured.overrides)) throw new Error('Missing lint overrides')
|
|
2858
|
-
const
|
|
3378
|
+
const overrides = configured.overrides
|
|
2859
3379
|
.map(readConfigRecord)
|
|
2860
3380
|
.filter(
|
|
2861
3381
|
(override) => readConfigRecord(override.rules)['import/no-dynamic-require'] !== undefined,
|
|
2862
3382
|
)
|
|
2863
|
-
expect(
|
|
3383
|
+
expect(overrides).toEqual([])
|
|
2864
3384
|
})
|
|
2865
3385
|
|
|
2866
3386
|
it('permits a computed import() and refuses a computed require at every module path', () => {
|
|
@@ -2923,107 +3443,519 @@ createSomething({
|
|
|
2923
3443
|
}
|
|
2924
3444
|
})
|
|
2925
3445
|
|
|
2926
|
-
it('
|
|
2927
|
-
|
|
2928
|
-
const
|
|
3446
|
+
it('computes Vue targets from lexical importer and workspace boundaries', () => {
|
|
3447
|
+
expect(POLICY_VUE_TARGET_CASES.length).toBeGreaterThan(30)
|
|
3448
|
+
for (const [filename, cwd, source, expected] of POLICY_VUE_TARGET_CASES) {
|
|
3449
|
+
expect({ filename, cwd, source, target: computePolicyTarget(filename, cwd, source) }).toEqual(
|
|
3450
|
+
{
|
|
3451
|
+
filename,
|
|
3452
|
+
cwd,
|
|
3453
|
+
source,
|
|
3454
|
+
target: expected,
|
|
3455
|
+
},
|
|
3456
|
+
)
|
|
3457
|
+
}
|
|
3458
|
+
})
|
|
3459
|
+
|
|
3460
|
+
it('partitions every Vue syntax input between parsed and native parser-refused coverage', () => {
|
|
3461
|
+
const originals = [...POLICY_VUE_IMPORT_CASES, ...POLICY_VUE_COMPUTED_CASES]
|
|
3462
|
+
const partition = [...POLICY_VUE_PARSED_CASES, ...POLICY_VUE_PARSER_CASES]
|
|
3463
|
+
expect(originals).toHaveLength(312)
|
|
3464
|
+
expect(new Set(originals.map(({ path }) => path)).size).toBe(originals.length)
|
|
3465
|
+
expect(POLICY_VUE_PARSED_CASES).toHaveLength(298)
|
|
3466
|
+
expect(POLICY_VUE_PARSER_CASES).toHaveLength(14)
|
|
3467
|
+
expect(new Set(partition.map(({ path }) => path)).size).toBe(partition.length)
|
|
3468
|
+
expect(partition.map(({ path }) => path).toSorted()).toEqual(
|
|
3469
|
+
originals.map(({ path }) => path).toSorted(),
|
|
3470
|
+
)
|
|
3471
|
+
for (const original of originals) {
|
|
3472
|
+
expect(partition.find((source) => source.path === original.path)).toMatchObject(original)
|
|
3473
|
+
}
|
|
3474
|
+
expect(POLICY_VUE_PARSER_CASES.map(({ path }) => path).toSorted()).toEqual(
|
|
3475
|
+
POLICY_VUE_FACES.flatMap((face) =>
|
|
3476
|
+
POLICY_VUE_PARSER_ROWS.map(([file]) => `${face}/${file}`),
|
|
3477
|
+
).toSorted(),
|
|
3478
|
+
)
|
|
3479
|
+
expect(POLICY_VUE_PARSER_CASES.every(({ path }) => path.endsWith('.cjs'))).toBe(true)
|
|
3480
|
+
expect([...new Set(POLICY_VUE_POPULATIONS.map(({ extension }) => extension))]).toEqual([
|
|
3481
|
+
'cjs',
|
|
3482
|
+
'cts',
|
|
3483
|
+
'js',
|
|
3484
|
+
'jsx',
|
|
3485
|
+
'mjs',
|
|
3486
|
+
'mts',
|
|
3487
|
+
'ts',
|
|
3488
|
+
'tsx',
|
|
3489
|
+
'vue',
|
|
3490
|
+
])
|
|
3491
|
+
for (const face of POLICY_VUE_FACES) {
|
|
3492
|
+
const sources = originals.filter((source) => source.path.startsWith(face + '/'))
|
|
3493
|
+
expect(sources).toHaveLength(156)
|
|
3494
|
+
for (const population of POLICY_VUE_POPULATIONS) {
|
|
3495
|
+
for (const form of POLICY_VUE_IMPORT_FORMS.filter(
|
|
3496
|
+
(item) => !item.typed || population.typed,
|
|
3497
|
+
)) {
|
|
3498
|
+
expect(sources).toContainEqual(
|
|
3499
|
+
expect.objectContaining({
|
|
3500
|
+
path: `${face}/${population.name}${form.name}.${population.extension}`,
|
|
3501
|
+
boundary: true,
|
|
3502
|
+
}),
|
|
3503
|
+
)
|
|
3504
|
+
expect(sources).toContainEqual(
|
|
3505
|
+
expect.objectContaining({
|
|
3506
|
+
path: `${face}/features/editor/${population.name}${form.name}.${population.extension}`,
|
|
3507
|
+
boundary: false,
|
|
3508
|
+
}),
|
|
3509
|
+
)
|
|
3510
|
+
}
|
|
3511
|
+
for (const index of POLICY_VUE_COMPUTED_FORMS.keys()) {
|
|
3512
|
+
expect(sources).toContainEqual(
|
|
3513
|
+
expect.objectContaining({
|
|
3514
|
+
path: `${face}/computed${population.name}${index}.${population.extension}`,
|
|
3515
|
+
boundary: false,
|
|
3516
|
+
}),
|
|
3517
|
+
)
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
})
|
|
3522
|
+
|
|
3523
|
+
it('checks Vue import syntax and populations through the canonical policy', () => {
|
|
3524
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-syntax-' })
|
|
2929
3525
|
try {
|
|
2930
|
-
|
|
2931
|
-
|
|
3526
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3527
|
+
fixture.write(
|
|
3528
|
+
'configs/policy.ts',
|
|
3529
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
2932
3530
|
)
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3531
|
+
for (const face of POLICY_VUE_FACES) {
|
|
3532
|
+
const sources = POLICY_VUE_PARSED_CASES.filter((source) =>
|
|
3533
|
+
source.path.startsWith(face + '/'),
|
|
3534
|
+
)
|
|
3535
|
+
expect(sources).toHaveLength(149)
|
|
3536
|
+
expect(sources).toContainEqual(
|
|
3537
|
+
expect.objectContaining({ path: `${face}/cjsdynamic.cjs`, boundary: true }),
|
|
3538
|
+
)
|
|
3539
|
+
expect(sources).toContainEqual(
|
|
3540
|
+
expect.objectContaining({
|
|
3541
|
+
path: `${face}/features/editor/cjsdynamic.cjs`,
|
|
3542
|
+
boundary: false,
|
|
3543
|
+
}),
|
|
3544
|
+
)
|
|
3545
|
+
for (const source of sources) fixture.write(source.path, source.content)
|
|
3546
|
+
const codes = readImportDiagnostics(
|
|
3547
|
+
fixture.path,
|
|
3548
|
+
sources.map(({ path }) => path),
|
|
3549
|
+
)
|
|
3550
|
+
expect(
|
|
3551
|
+
sources.map(({ path }) => ({
|
|
3552
|
+
path,
|
|
3553
|
+
boundary: codes.has('policy(no-cross-vue-face) ' + path),
|
|
3554
|
+
restricted: codes.has('eslint(no-restricted-imports) ' + path),
|
|
3555
|
+
})),
|
|
3556
|
+
).toEqual(sources.map(({ path, boundary, restricted }) => ({ path, boundary, restricted })))
|
|
3557
|
+
}
|
|
3558
|
+
} finally {
|
|
3559
|
+
fixture.destroy()
|
|
3560
|
+
}
|
|
3561
|
+
})
|
|
3562
|
+
|
|
3563
|
+
it('retains exact native parser refusals for CommonJS Vue inputs on both faces', () => {
|
|
3564
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-parser-' })
|
|
3565
|
+
try {
|
|
3566
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3567
|
+
fixture.write(
|
|
3568
|
+
'configs/policy.ts',
|
|
3569
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
3570
|
+
)
|
|
3571
|
+
const sources = POLICY_VUE_PARSER_CASES
|
|
3572
|
+
const paths = sources.map(({ path }) => path)
|
|
3573
|
+
expect(paths).toHaveLength(14)
|
|
3574
|
+
for (const source of sources) fixture.write(source.path, source.content)
|
|
3575
|
+
const diagnostics = readNativeDiagnostics(fixture.path, paths)
|
|
3576
|
+
expect(diagnostics).toHaveLength(paths.length)
|
|
3577
|
+
const comparisonRoot = realpathSync.native(fixture.path)
|
|
3578
|
+
const observed = diagnostics.map((diagnostic) => {
|
|
3579
|
+
if (typeof diagnostic.filename !== 'string')
|
|
3580
|
+
throw new Error('Missing parser diagnostic filename: ' + JSON.stringify(diagnostic))
|
|
3581
|
+
const path = normalizePolicyFilename(comparisonRoot, diagnostic.filename)
|
|
3582
|
+
const source = sources.find((candidate) => candidate.path === path)
|
|
3583
|
+
if (source === undefined)
|
|
3584
|
+
throw new Error('Unexpected parser diagnostic: ' + JSON.stringify(diagnostic))
|
|
3585
|
+
expect(diagnostic).toEqual({
|
|
3586
|
+
message: source.message,
|
|
3587
|
+
severity: 'error',
|
|
3588
|
+
filename: diagnostic.filename,
|
|
3589
|
+
labels: [{ span: source.span }],
|
|
3590
|
+
})
|
|
3591
|
+
expect(
|
|
3592
|
+
source.content.slice(source.span.offset, source.span.offset + source.span.length),
|
|
3593
|
+
).toBe(source.keyword)
|
|
3594
|
+
return path
|
|
3595
|
+
})
|
|
3596
|
+
expect(observed.toSorted()).toEqual(paths.toSorted())
|
|
3597
|
+
} finally {
|
|
3598
|
+
fixture.destroy()
|
|
3599
|
+
}
|
|
3600
|
+
})
|
|
3601
|
+
|
|
3602
|
+
it('retains Vue independent restrictions beside exact relative diagnostics', () => {
|
|
3603
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-boundaries-' })
|
|
3604
|
+
try {
|
|
3605
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3606
|
+
fixture.write(
|
|
3607
|
+
'configs/policy.ts',
|
|
3608
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
3609
|
+
)
|
|
3610
|
+
const sources = POLICY_VUE_BOUNDARY_CASES
|
|
3611
|
+
expect(sources.length).toBeGreaterThan(100)
|
|
3612
|
+
for (const source of sources) fixture.write(source.path, source.content)
|
|
3613
|
+
const codes = readImportDiagnostics(
|
|
3614
|
+
fixture.path,
|
|
3615
|
+
sources.map(({ path }) => path),
|
|
3616
|
+
)
|
|
3617
|
+
expect(
|
|
3618
|
+
sources.map(({ path }) => ({
|
|
3619
|
+
path,
|
|
3620
|
+
boundary: codes.has('policy(no-cross-vue-face) ' + path),
|
|
3621
|
+
restricted: codes.has('eslint(no-restricted-imports) ' + path),
|
|
3622
|
+
})),
|
|
3623
|
+
).toEqual(sources.map(({ path, boundary, restricted }) => ({ path, boundary, restricted })))
|
|
3624
|
+
} finally {
|
|
3625
|
+
fixture.destroy()
|
|
3626
|
+
}
|
|
3627
|
+
})
|
|
3628
|
+
|
|
3629
|
+
it('detects suppressed Vue reporting in a scratch policy without weakening independent bans', () => {
|
|
3630
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-report-control-' })
|
|
3631
|
+
try {
|
|
3632
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3633
|
+
const plugin = readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8')
|
|
3634
|
+
const marker = "context.report({ node, messageId: 'vue' })"
|
|
3635
|
+
expect(plugin.split(marker)).toHaveLength(2)
|
|
3636
|
+
fixture.write('configs/policy.ts', plugin)
|
|
3637
|
+
const sources = POLICY_VUE_BOUNDARY_CASES
|
|
3638
|
+
for (const source of sources) fixture.write(source.path, source.content)
|
|
3639
|
+
const paths = sources.map(({ path }) => path)
|
|
3640
|
+
const enabled = readImportDiagnostics(fixture.path, paths)
|
|
3641
|
+
const expected = sources.filter(({ boundary }) => boundary).map(({ path }) => path)
|
|
3642
|
+
expect(expected).toContain('src/vue/boundary0.ts')
|
|
3643
|
+
expect(expected).toContain('app/vue/boundary0.ts')
|
|
3644
|
+
expect(paths.filter((path) => enabled.has('policy(no-cross-vue-face) ' + path))).toEqual(
|
|
3645
|
+
expected,
|
|
3646
|
+
)
|
|
3647
|
+
fixture.write('configs/policy.ts', plugin.replace(marker, 'void node'))
|
|
3648
|
+
const suppressed = readImportDiagnostics(fixture.path, paths)
|
|
3649
|
+
const actual = paths.filter((path) => suppressed.has('policy(no-cross-vue-face) ' + path))
|
|
3650
|
+
expect(actual).toEqual([])
|
|
3651
|
+
expect(() => expect(actual).toEqual(expected)).toThrow('deeply equal')
|
|
3652
|
+
for (const { path, restricted } of sources) {
|
|
3653
|
+
expect({ path, enabled: enabled.has('eslint(no-restricted-imports) ' + path) }).toEqual({
|
|
3654
|
+
path,
|
|
3655
|
+
enabled: restricted,
|
|
3656
|
+
})
|
|
3657
|
+
expect({
|
|
3658
|
+
path,
|
|
3659
|
+
suppressed: suppressed.has('eslint(no-restricted-imports) ' + path),
|
|
3660
|
+
}).toEqual({ path, suppressed: restricted })
|
|
3661
|
+
}
|
|
3662
|
+
} finally {
|
|
3663
|
+
fixture.destroy()
|
|
3664
|
+
}
|
|
3665
|
+
})
|
|
3666
|
+
|
|
3667
|
+
it('detects restored Vue relative regexes against nested canonical imports', () => {
|
|
3668
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-regex-control-' })
|
|
3669
|
+
try {
|
|
3670
|
+
const text = readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8')
|
|
3671
|
+
fixture.write('.oxlintrc.json', text)
|
|
3672
|
+
fixture.write(
|
|
3673
|
+
'configs/policy.ts',
|
|
3674
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
3675
|
+
)
|
|
3676
|
+
for (const source of POLICY_VUE_RELATIVE_SOURCES) fixture.write(source.path, source.content)
|
|
3677
|
+
const paths = POLICY_VUE_RELATIVE_SOURCES.map(({ path }) => path)
|
|
3678
|
+
const enabled = readImportDiagnostics(fixture.path, paths)
|
|
3679
|
+
expect(paths.filter((path) => enabled.has('eslint(no-restricted-imports) ' + path))).toEqual(
|
|
3680
|
+
[],
|
|
3681
|
+
)
|
|
3682
|
+
expect(paths.filter((path) => enabled.has('policy(no-cross-vue-face) ' + path))).toEqual([])
|
|
3683
|
+
const restoredConfiguration = readConfigRecord(JSON.parse(text))
|
|
3684
|
+
if (!Array.isArray(restoredConfiguration.overrides)) throw new Error('Missing lint overrides')
|
|
3685
|
+
const restored = restoredConfiguration.overrides.map((value: unknown) => {
|
|
3686
|
+
const block = readConfigRecord(value)
|
|
3687
|
+
const files = block.files
|
|
3688
|
+
const owner = POLICY_VUE_FACES.find(
|
|
3689
|
+
(face) =>
|
|
3690
|
+
Array.isArray(files) &&
|
|
3691
|
+
files.some((file: unknown) => typeof file === 'string' && file.startsWith(face + '/')),
|
|
3692
|
+
)
|
|
3693
|
+
if (owner === undefined) return block
|
|
3694
|
+
const original = POLICY_VUE_ORIGINAL_RELATIVES[owner]
|
|
3695
|
+
if (original === undefined) throw new Error('Missing original Vue relative pattern')
|
|
3696
|
+
const rules = readConfigRecord(block.rules)
|
|
3697
|
+
const rule = rules['no-restricted-imports']
|
|
3698
|
+
if (!Array.isArray(rule)) throw new Error('Missing Vue import restrictions')
|
|
3699
|
+
const options = readConfigRecord(rule[1])
|
|
3700
|
+
if (!Array.isArray(options.patterns)) throw new Error('Missing Vue import patterns')
|
|
3701
|
+
const patterns = options.patterns.map((patternValue: unknown): Record<string, unknown> => {
|
|
3702
|
+
const pattern = readConfigRecord(patternValue)
|
|
3703
|
+
if (pattern.message !== 'Vue faces must not depend on sibling sheet faces') return pattern
|
|
3704
|
+
if (typeof pattern.regex !== 'string' || !pattern.regex.endsWith(')'))
|
|
3705
|
+
throw new Error('Missing Vue sibling alias pattern')
|
|
3706
|
+
return { ...pattern, regex: pattern.regex.slice(0, -1) + '|' + original + ')' }
|
|
3707
|
+
})
|
|
3708
|
+
expect(
|
|
3709
|
+
patterns.filter(
|
|
3710
|
+
(pattern) => pattern.message === 'Vue faces must not depend on sibling sheet faces',
|
|
3711
|
+
),
|
|
3712
|
+
).toHaveLength(1)
|
|
3713
|
+
return {
|
|
3714
|
+
...block,
|
|
3715
|
+
rules: { ...rules, 'no-restricted-imports': [rule[0], { ...options, patterns }] },
|
|
3716
|
+
}
|
|
3717
|
+
})
|
|
3718
|
+
expect(
|
|
3719
|
+
restored.filter(
|
|
3720
|
+
(block) => readConfigRecord(block.rules)['policy/no-cross-vue-face'] === 'error',
|
|
3721
|
+
),
|
|
3722
|
+
).toHaveLength(2)
|
|
3723
|
+
fixture.write(
|
|
3724
|
+
'.oxlintrc.json',
|
|
3725
|
+
JSON.stringify({ ...restoredConfiguration, overrides: restored }),
|
|
3726
|
+
)
|
|
3727
|
+
const control = readImportDiagnostics(fixture.path, paths)
|
|
3728
|
+
const refused = paths.filter((path) => control.has('eslint(no-restricted-imports) ' + path))
|
|
3729
|
+
expect(refused).toEqual(paths)
|
|
3730
|
+
expect(() => expect(refused).toEqual([])).toThrow('deeply equal')
|
|
3731
|
+
expect(paths.filter((path) => control.has('policy(no-cross-vue-face) ' + path))).toEqual([])
|
|
3732
|
+
} finally {
|
|
3733
|
+
fixture.destroy()
|
|
3734
|
+
}
|
|
3735
|
+
})
|
|
3736
|
+
|
|
3737
|
+
it('checks exact Vue composable membership and malformed modules through the canonical policy', () => {
|
|
3738
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-domain-' })
|
|
3739
|
+
try {
|
|
3740
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3741
|
+
fixture.write(
|
|
3742
|
+
'configs/policy.ts',
|
|
3743
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
3744
|
+
)
|
|
3745
|
+
const sources = POLICY_VUE_DOMAIN_CASES
|
|
3746
|
+
expect(sources).toContainEqual({
|
|
3747
|
+
...POLICY_VUE_COMPOSABLE_SOURCE,
|
|
3748
|
+
misplaced: false,
|
|
3749
|
+
malformed: false,
|
|
3750
|
+
})
|
|
3751
|
+
for (const source of sources) fixture.write(source.path, source.content)
|
|
3752
|
+
const codes = readImportDiagnostics(
|
|
3753
|
+
fixture.path,
|
|
3754
|
+
sources.map(({ path }) => path),
|
|
3755
|
+
)
|
|
3756
|
+
expect(
|
|
3757
|
+
sources.map(({ path }) => ({
|
|
3758
|
+
path,
|
|
3759
|
+
misplaced: codes.has('policy(no-misplaced-function) ' + path),
|
|
3760
|
+
malformed: codes.has('policy(no-malformed-domain) ' + path),
|
|
3761
|
+
})),
|
|
3762
|
+
).toEqual(sources.map(({ path, misplaced, malformed }) => ({ path, misplaced, malformed })))
|
|
3763
|
+
} finally {
|
|
3764
|
+
fixture.destroy()
|
|
3765
|
+
}
|
|
3766
|
+
})
|
|
3767
|
+
|
|
3768
|
+
it('detects a removed Vue composable registration in a scratch policy', () => {
|
|
3769
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-domain-control-' })
|
|
3770
|
+
try {
|
|
3771
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3772
|
+
const plugin = readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8')
|
|
3773
|
+
const registration = /^\t'app\/vue\/composables',\r?\n/gmu
|
|
3774
|
+
expect(plugin.match(registration)).toHaveLength(1)
|
|
3775
|
+
fixture.write('configs/policy.ts', plugin)
|
|
3776
|
+
const source = POLICY_VUE_COMPOSABLE_SOURCE
|
|
3777
|
+
fixture.write(source.path, source.content)
|
|
3778
|
+
const enabled = readImportDiagnostics(fixture.path, [source.path])
|
|
3779
|
+
expect(enabled.has('policy(no-misplaced-function) ' + source.path)).toBe(false)
|
|
3780
|
+
expect(enabled.has('policy(no-malformed-domain) ' + source.path)).toBe(false)
|
|
3781
|
+
fixture.write('configs/policy.ts', plugin.replace(registration, ''))
|
|
3782
|
+
const control = readImportDiagnostics(fixture.path, [source.path])
|
|
3783
|
+
expect(control.has('policy(no-misplaced-function) ' + source.path)).toBe(true)
|
|
3784
|
+
expect(control.has('policy(no-malformed-domain) ' + source.path)).toBe(false)
|
|
3785
|
+
expect(() =>
|
|
3786
|
+
expect(control.has('policy(no-misplaced-function) ' + source.path)).toBe(false),
|
|
3787
|
+
).toThrow('expected')
|
|
3788
|
+
} finally {
|
|
3789
|
+
fixture.destroy()
|
|
3790
|
+
}
|
|
3791
|
+
})
|
|
3792
|
+
|
|
3793
|
+
it('admits nested same-face Vue relative imports through the canonical policy', () => {
|
|
3794
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-relative-' })
|
|
3795
|
+
try {
|
|
3796
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3797
|
+
fixture.write(
|
|
3798
|
+
'configs/policy.ts',
|
|
3799
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
3800
|
+
)
|
|
3801
|
+
for (const source of POLICY_VUE_RELATIVE_SOURCES) {
|
|
3802
|
+
fixture.write(source.path, source.content)
|
|
3803
|
+
}
|
|
3804
|
+
const paths = POLICY_VUE_RELATIVE_SOURCES.map(({ path }) => path)
|
|
3805
|
+
expect(paths).toContain('app/vue/components/Panel.vue')
|
|
3806
|
+
expect(paths).toContain('src/vue/features/helpers.ts')
|
|
3807
|
+
const codes = readImportDiagnostics(fixture.path, paths)
|
|
3808
|
+
const refused = paths.filter(
|
|
3809
|
+
(path) =>
|
|
3810
|
+
codes.has('eslint(no-restricted-imports) ' + path) ||
|
|
3811
|
+
codes.has('policy(no-cross-vue-face) ' + path),
|
|
3812
|
+
)
|
|
3813
|
+
expect(refused).toEqual([])
|
|
3814
|
+
} finally {
|
|
3815
|
+
fixture.destroy()
|
|
3816
|
+
}
|
|
3817
|
+
})
|
|
3818
|
+
|
|
3819
|
+
it('admits a valid direct application Vue composable through the canonical policy', () => {
|
|
3820
|
+
const fixture = createPolicyScratch({ prefix: 'policy-vue-composable-' })
|
|
3821
|
+
try {
|
|
3822
|
+
fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
3823
|
+
fixture.write(
|
|
3824
|
+
'configs/policy.ts',
|
|
3825
|
+
readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
|
|
3826
|
+
)
|
|
3827
|
+
const source = POLICY_VUE_COMPOSABLE_SOURCE
|
|
3828
|
+
expect(source.path).toBe('app/vue/composables/useTheme.ts')
|
|
3829
|
+
fixture.write(source.path, source.content)
|
|
3830
|
+
const codes = readImportDiagnostics(fixture.path, [source.path])
|
|
3831
|
+
expect({
|
|
3832
|
+
misplaced: codes.has('policy(no-misplaced-function) ' + source.path),
|
|
3833
|
+
malformed: codes.has('policy(no-malformed-domain) ' + source.path),
|
|
3834
|
+
}).toEqual({ misplaced: false, malformed: false })
|
|
3835
|
+
} finally {
|
|
3836
|
+
fixture.destroy()
|
|
3837
|
+
}
|
|
3838
|
+
})
|
|
3839
|
+
|
|
3840
|
+
it('matches every isolated import pattern with refused and admitted fixtures', () => {
|
|
3841
|
+
const fixture = createPolicyScratch({ prefix: 'propagation-patterns-' })
|
|
3842
|
+
const scratch = fixture.path
|
|
3843
|
+
try {
|
|
3844
|
+
const configured = readConfigRecord(
|
|
3845
|
+
JSON.parse(readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8')),
|
|
3846
|
+
)
|
|
3847
|
+
if (!Array.isArray(configured.overrides)) throw new Error('Missing overrides')
|
|
3848
|
+
const built = configured.overrides.flatMap((value: unknown, blockIndex: number) => {
|
|
3849
|
+
const block = readConfigRecord(value)
|
|
3850
|
+
const rule = readConfigRecord(block.rules)['no-restricted-imports']
|
|
3851
|
+
if (!Array.isArray(rule)) return []
|
|
3852
|
+
const patterns = readConfigRecord(rule[1]).patterns
|
|
3853
|
+
if (!Array.isArray(patterns)) throw new Error('Missing restriction patterns')
|
|
3854
|
+
const fromPatterns = patterns.map((patternValue: unknown, patternIndex: number) => {
|
|
3855
|
+
const pattern = readConfigRecord(patternValue)
|
|
3856
|
+
const message = pattern.message
|
|
3857
|
+
if (typeof message !== 'string') throw new Error('Missing pattern message')
|
|
3858
|
+
const alias = /@src\/(core|browser|server) alias/u.exec(message)?.[1]
|
|
3859
|
+
const fence = POLICY_IMPORT_CASES.find(({ marker }) => message.includes(marker))
|
|
3860
|
+
const chained: ReadonlyArray<readonly [string, boolean]> =
|
|
3861
|
+
alias !== undefined
|
|
3862
|
+
? [
|
|
3863
|
+
[`../${alias}/index.js`, true],
|
|
3864
|
+
[`../../${alias}/types.js`, true],
|
|
3865
|
+
[`../../src/${alias}/index.js`, true],
|
|
3866
|
+
[`../${alias}`, true],
|
|
3867
|
+
[`../${alias}/`, true],
|
|
3868
|
+
[`../${alias}?raw`, true],
|
|
3869
|
+
[`../${alias}#fragment`, true],
|
|
3870
|
+
[`@src/${alias}`, false],
|
|
3871
|
+
[`./${alias}/index.js`, false],
|
|
3872
|
+
[`../${alias}x/index.js`, false],
|
|
3873
|
+
[`../${alias}.ext/index.js`, false],
|
|
3874
|
+
]
|
|
3875
|
+
: message.includes('self-contained')
|
|
3876
|
+
? [
|
|
3877
|
+
['./helpers.ts', true],
|
|
3878
|
+
['../../orkestrel-dispatch/scripts/helpers.ts', true],
|
|
3879
|
+
['@orkestrel/test', true],
|
|
3880
|
+
['fs', true],
|
|
3881
|
+
['', true],
|
|
3882
|
+
['n', true],
|
|
3883
|
+
['no', true],
|
|
3884
|
+
['nod', true],
|
|
3885
|
+
['node', true],
|
|
3886
|
+
['nodes:fs', true],
|
|
3887
|
+
['node:fs', false],
|
|
3888
|
+
['node:child_process', false],
|
|
2971
3889
|
]
|
|
2972
|
-
: message.includes('
|
|
3890
|
+
: message.includes('URL schemes')
|
|
2973
3891
|
? [
|
|
2974
|
-
['
|
|
2975
|
-
['
|
|
2976
|
-
['
|
|
2977
|
-
['
|
|
2978
|
-
['
|
|
2979
|
-
['
|
|
2980
|
-
['
|
|
3892
|
+
['https://host/x', true],
|
|
3893
|
+
['data:text/plain,x', true],
|
|
3894
|
+
['node:fs', false],
|
|
3895
|
+
['n:entry', true],
|
|
3896
|
+
['no:entry', true],
|
|
3897
|
+
['nod:entry', true],
|
|
3898
|
+
['nodea:entry', true],
|
|
3899
|
+
['1bad:entry', false],
|
|
3900
|
+
['./data:entry', false],
|
|
2981
3901
|
]
|
|
2982
|
-
: message.includes('
|
|
3902
|
+
: message.includes('noncanonical dot')
|
|
2983
3903
|
? [
|
|
2984
|
-
['
|
|
2985
|
-
['../
|
|
2986
|
-
['
|
|
2987
|
-
['
|
|
2988
|
-
['
|
|
2989
|
-
['
|
|
2990
|
-
['
|
|
2991
|
-
['@src/corex', true],
|
|
2992
|
-
['@src/', true],
|
|
2993
|
-
['../src/print/index.js', true],
|
|
2994
|
-
['../browser?x', true],
|
|
2995
|
-
['../print.ext/index.js', false],
|
|
2996
|
-
...(Array.isArray(block.files) &&
|
|
2997
|
-
block.files.some(
|
|
2998
|
-
(file: unknown) =>
|
|
2999
|
-
typeof file === 'string' && file.startsWith('app/vue/'),
|
|
3000
|
-
)
|
|
3001
|
-
? [['@app/print', true] as const]
|
|
3002
|
-
: []),
|
|
3003
|
-
]
|
|
3004
|
-
: [
|
|
3005
|
-
[
|
|
3006
|
-
message.includes('absolute paths')
|
|
3007
|
-
? '/machine/module.js'
|
|
3008
|
-
: message.includes('normalized traversal')
|
|
3009
|
-
? './../core/index.js'
|
|
3010
|
-
: message.includes('forward slashes')
|
|
3011
|
-
? '..\\core\\index.js'
|
|
3012
|
-
: message.includes('compiler API')
|
|
3013
|
-
? 'typescript'
|
|
3014
|
-
: message.includes('private app')
|
|
3015
|
-
? '@app/core'
|
|
3016
|
-
: message.includes('stylesheets')
|
|
3017
|
-
? './index.css'
|
|
3018
|
-
: message.includes('host-independent') ||
|
|
3019
|
-
message.includes('Node or server')
|
|
3020
|
-
? 'node:fs'
|
|
3021
|
-
: message.includes('Vue extension')
|
|
3022
|
-
? '@src/vue'
|
|
3023
|
-
: '@src/browser',
|
|
3024
|
-
true,
|
|
3025
|
-
],
|
|
3904
|
+
['../core/../server/index.js', true],
|
|
3905
|
+
['../core/./index.js', true],
|
|
3906
|
+
['../core/index.js', false],
|
|
3907
|
+
['./index.js', false],
|
|
3908
|
+
['../../core/index.js', false],
|
|
3909
|
+
['.../../index.js', true],
|
|
3910
|
+
['.name/../index.js', true],
|
|
3026
3911
|
]
|
|
3912
|
+
: message.includes('sibling sheet')
|
|
3913
|
+
? [
|
|
3914
|
+
['@src/print', true],
|
|
3915
|
+
// Relative refusals now belong to the canonical filename-based matrix.
|
|
3916
|
+
['@src/core', false],
|
|
3917
|
+
['@src/browser', false],
|
|
3918
|
+
['@src/vue', false],
|
|
3919
|
+
['@app/core', false],
|
|
3920
|
+
['@app/browser', false],
|
|
3921
|
+
['@src/corex', true],
|
|
3922
|
+
['@src/', true],
|
|
3923
|
+
// The app browser query is admitted there; src retains its alias refusal.
|
|
3924
|
+
['../browser?x', false],
|
|
3925
|
+
['../print.ext/index.js', false],
|
|
3926
|
+
...(Array.isArray(block.files) &&
|
|
3927
|
+
block.files.some(
|
|
3928
|
+
(file: unknown) =>
|
|
3929
|
+
typeof file === 'string' && file.startsWith('app/vue/'),
|
|
3930
|
+
)
|
|
3931
|
+
? [['@app/print', true] as const]
|
|
3932
|
+
: []),
|
|
3933
|
+
]
|
|
3934
|
+
: [
|
|
3935
|
+
[
|
|
3936
|
+
message.includes('absolute paths')
|
|
3937
|
+
? '/machine/module.js'
|
|
3938
|
+
: message.includes('normalized traversal')
|
|
3939
|
+
? './../core/index.js'
|
|
3940
|
+
: message.includes('forward slashes')
|
|
3941
|
+
? '..\\core\\index.js'
|
|
3942
|
+
: message.includes('compiler API')
|
|
3943
|
+
? 'typescript'
|
|
3944
|
+
: message.includes('private app')
|
|
3945
|
+
? '@app/core'
|
|
3946
|
+
: message.includes('stylesheets')
|
|
3947
|
+
? './index.css'
|
|
3948
|
+
: message.includes('host-independent') ||
|
|
3949
|
+
message.includes('Node or server')
|
|
3950
|
+
? 'node:fs'
|
|
3951
|
+
: message.includes('Vue extension')
|
|
3952
|
+
? '@src/vue'
|
|
3953
|
+
: '@src/browser',
|
|
3954
|
+
true,
|
|
3955
|
+
],
|
|
3956
|
+
]
|
|
3957
|
+
// A setup-module fence lists its own refused and admitted specifiers in the setup module.
|
|
3958
|
+
const listed = fence?.cases ?? chained
|
|
3027
3959
|
const face =
|
|
3028
3960
|
message.includes('another styles face') && typeof pattern.regex === 'string'
|
|
3029
3961
|
? /@src\/(bootstrap|tailwindcss|styles)\(/u.exec(pattern.regex)?.[1]
|
|
@@ -3044,24 +3976,31 @@ createSomething({
|
|
|
3044
3976
|
[`@src/${face}x`, false],
|
|
3045
3977
|
[`../${face}.ext/index.js`, false],
|
|
3046
3978
|
]
|
|
3979
|
+
// A per-file glob such as `.agents/skills/*/scripts/*.ts` or `tests/setup.ts` names a
|
|
3980
|
+
// directory once its file part is dropped and each wildcard segment takes a sample name.
|
|
3047
3981
|
const owner =
|
|
3048
3982
|
Array.isArray(block.files) && typeof block.files[0] === 'string'
|
|
3049
|
-
? block.files[0]
|
|
3983
|
+
? block.files[0]
|
|
3984
|
+
.split('/**')[0]
|
|
3985
|
+
?.replace(/\/[^/]*\.[a-z]+$/u, '')
|
|
3986
|
+
.replaceAll('*', 'sample')
|
|
3050
3987
|
: undefined
|
|
3051
3988
|
if (owner === undefined) throw new Error('Missing pattern owner')
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3989
|
+
// Every relative specifier is refused in a self-contained script and in the policy
|
|
3990
|
+
// plugin, which imports nothing, so their admitted controls are the rows above.
|
|
3991
|
+
const controls =
|
|
3992
|
+
message.includes('self-contained') || fence?.controlled === false
|
|
3993
|
+
? []
|
|
3994
|
+
: [['./index.js', false] as const]
|
|
3995
|
+
const cases = [...sources, ...controls].map(([source, refusal], index) => {
|
|
3996
|
+
const path = `${owner}/pattern${blockIndex}_${patternIndex}_${index}.ts`
|
|
3997
|
+
mkdirSync(dirname(resolve(scratch, path)), { recursive: true })
|
|
3998
|
+
writeFileSync(
|
|
3999
|
+
resolve(scratch, path),
|
|
4000
|
+
'import * as boundary from ' + JSON.stringify(source) + '\nvoid boundary\ndebugger\n',
|
|
4001
|
+
)
|
|
4002
|
+
return { path, refusal }
|
|
4003
|
+
})
|
|
3065
4004
|
return {
|
|
3066
4005
|
cases,
|
|
3067
4006
|
override: {
|
|
@@ -3070,7 +4009,60 @@ createSomething({
|
|
|
3070
4009
|
},
|
|
3071
4010
|
}
|
|
3072
4011
|
})
|
|
4012
|
+
// A path entry restricts named imports of one module, so each case imports one clause.
|
|
4013
|
+
const entries = readConfigRecord(rule[1]).paths
|
|
4014
|
+
const fromPaths = (Array.isArray(entries) ? entries : []).map(
|
|
4015
|
+
(entryValue: unknown, entryIndex: number) => {
|
|
4016
|
+
const entry = readConfigRecord(entryValue)
|
|
4017
|
+
const message = entry.message
|
|
4018
|
+
if (typeof message !== 'string' || typeof entry.name !== 'string') {
|
|
4019
|
+
throw new Error('Missing path entry message or module')
|
|
4020
|
+
}
|
|
4021
|
+
const named = POLICY_NAME_CASES.find(({ marker }) => message.includes(marker))
|
|
4022
|
+
if (named === undefined) throw new Error('Missing named import cases for a path entry')
|
|
4023
|
+
const owner =
|
|
4024
|
+
Array.isArray(block.files) && typeof block.files[0] === 'string'
|
|
4025
|
+
? block.files[0].replace(/\/[^/]*\.[a-z]+$/u, '').replaceAll('*', 'sample')
|
|
4026
|
+
: undefined
|
|
4027
|
+
if (owner === undefined) throw new Error('Missing path owner')
|
|
4028
|
+
const cases = [
|
|
4029
|
+
...named.refused.map((clause): [string, boolean] => [clause, true]),
|
|
4030
|
+
...named.admitted.map((clause): [string, boolean] => [clause, false]),
|
|
4031
|
+
].map(([clause, refusal], index) => {
|
|
4032
|
+
const path = `${owner}/path${blockIndex}_${entryIndex}_${index}.ts`
|
|
4033
|
+
mkdirSync(dirname(resolve(scratch, path)), { recursive: true })
|
|
4034
|
+
writeFileSync(
|
|
4035
|
+
resolve(scratch, path),
|
|
4036
|
+
`import { ${clause} } from ${JSON.stringify(entry.name)}\ndebugger\n`,
|
|
4037
|
+
)
|
|
4038
|
+
return { path, refusal }
|
|
4039
|
+
})
|
|
4040
|
+
return {
|
|
4041
|
+
cases,
|
|
4042
|
+
override: {
|
|
4043
|
+
files: cases.map(({ path }) => path),
|
|
4044
|
+
rules: { 'no-restricted-imports': ['error', { paths: [entry] }] },
|
|
4045
|
+
},
|
|
4046
|
+
}
|
|
4047
|
+
},
|
|
4048
|
+
)
|
|
4049
|
+
return [...fromPatterns, ...fromPaths]
|
|
3073
4050
|
})
|
|
4051
|
+
// Each fence keeps a pattern or path entry in the configuration, so deleting one fails here
|
|
4052
|
+
// rather than leaving the loop above with nothing to read.
|
|
4053
|
+
const messages = configured.overrides.flatMap((value: unknown) => {
|
|
4054
|
+
const rule = readConfigRecord(readConfigRecord(value).rules)['no-restricted-imports']
|
|
4055
|
+
if (!Array.isArray(rule)) return []
|
|
4056
|
+
const options = readConfigRecord(rule[1])
|
|
4057
|
+
return [options.patterns, options.paths]
|
|
4058
|
+
.flatMap((list: unknown) => (Array.isArray(list) ? list : []))
|
|
4059
|
+
.map((entry: unknown) => String(readConfigRecord(entry).message))
|
|
4060
|
+
})
|
|
4061
|
+
expect(
|
|
4062
|
+
[...POLICY_IMPORT_CASES, ...POLICY_NAME_CASES]
|
|
4063
|
+
.map(({ marker }) => marker)
|
|
4064
|
+
.filter((marker) => !messages.some((message) => message.includes(marker))),
|
|
4065
|
+
).toEqual([])
|
|
3074
4066
|
const overrides = built.map(({ override }) => override)
|
|
3075
4067
|
const expected = new Map(
|
|
3076
4068
|
built.flatMap(({ cases }) =>
|
|
@@ -3082,12 +4074,12 @@ createSomething({
|
|
|
3082
4074
|
JSON.stringify({ rules: { 'no-debugger': 'error' }, overrides }),
|
|
3083
4075
|
)
|
|
3084
4076
|
const codes = readImportDiagnostics(scratch, [...expected.keys()])
|
|
3085
|
-
const
|
|
4077
|
+
const verdicts = [...expected].map(([path, refusal]) => ({
|
|
3086
4078
|
path,
|
|
3087
4079
|
expected: refusal,
|
|
3088
4080
|
actual: codes.has('eslint(no-restricted-imports) ' + path),
|
|
3089
4081
|
}))
|
|
3090
|
-
expect(
|
|
4082
|
+
expect(verdicts.filter((verdict) => verdict.expected !== verdict.actual)).toEqual([])
|
|
3091
4083
|
writeFileSync(
|
|
3092
4084
|
resolve(scratch, '.oxlintrc.json'),
|
|
3093
4085
|
JSON.stringify({ rules: { 'no-debugger': 'error', 'no-restricted-imports': 'off' } }),
|
|
@@ -3299,19 +4291,23 @@ createSomething({
|
|
|
3299
4291
|
const [enabled, disabled] = reports
|
|
3300
4292
|
if (enabled === undefined || disabled === undefined)
|
|
3301
4293
|
throw new Error('Missing boundary readings')
|
|
3302
|
-
const
|
|
4294
|
+
const verdicts = [...expected].map(([path, refusal]) => ({
|
|
3303
4295
|
path,
|
|
3304
4296
|
expected: refusal,
|
|
3305
|
-
actual:
|
|
4297
|
+
actual:
|
|
4298
|
+
enabled.has('eslint(no-restricted-imports) ' + path) ||
|
|
4299
|
+
enabled.has('policy(no-cross-vue-face) ' + path),
|
|
3306
4300
|
}))
|
|
3307
|
-
expect(
|
|
4301
|
+
expect(verdicts.filter((verdict) => verdict.actual !== verdict.expected)).toEqual([])
|
|
3308
4302
|
const control = [...expected].map(([path, refusal]) => ({
|
|
3309
4303
|
path,
|
|
3310
4304
|
expected: refusal,
|
|
3311
|
-
actual:
|
|
4305
|
+
actual:
|
|
4306
|
+
disabled.has('eslint(no-restricted-imports) ' + path) ||
|
|
4307
|
+
disabled.has('policy(no-cross-vue-face) ' + path),
|
|
3312
4308
|
}))
|
|
3313
4309
|
expect(() =>
|
|
3314
|
-
expect(control.filter((
|
|
4310
|
+
expect(control.filter((verdict) => verdict.actual !== verdict.expected)).toEqual([]),
|
|
3315
4311
|
).toThrow('deeply equal')
|
|
3316
4312
|
expect(control).toContainEqual({ path: 'src/vue/refused3.ts', expected: true, actual: false })
|
|
3317
4313
|
expect(control).toContainEqual({ path: 'src/vue/refused8.ts', expected: true, actual: false })
|
|
@@ -3329,13 +4325,53 @@ createSomething({
|
|
|
3329
4325
|
const declared = Object.getOwnPropertyNames(rules)
|
|
3330
4326
|
expect(declared).toContain('no-misplaced-function')
|
|
3331
4327
|
expect(declared).toContain('no-host-line-endings')
|
|
4328
|
+
expect(declared).toContain('no-cross-vue-face')
|
|
4329
|
+
expect(declared).toContain('no-malformed-script')
|
|
4330
|
+
expect(declared).toContain('no-mutable-property')
|
|
3332
4331
|
expect(
|
|
3333
4332
|
inspectPolicyWiring(
|
|
3334
4333
|
parsed,
|
|
3335
4334
|
declared.map((name) => `policy/${name}`),
|
|
3336
|
-
[POLICY_PLACEMENT_GLOBS, POLICY_ENDING_GLOBS],
|
|
4335
|
+
[POLICY_PLACEMENT_GLOBS, POLICY_ENDING_GLOBS, POLICY_SCRIPT_GLOBS, POLICY_NESTED_GLOBS],
|
|
3337
4336
|
),
|
|
3338
4337
|
).toEqual([])
|
|
4338
|
+
// The controls: a configuration with the skill-script override or the nested-function
|
|
4339
|
+
// override removed reports the population it no longer declares.
|
|
4340
|
+
const overrides: unknown = Object.getOwnPropertyDescriptor(parsed, 'overrides')?.value
|
|
4341
|
+
if (!Array.isArray(overrides)) throw new Error('The Oxlint configuration has no overrides')
|
|
4342
|
+
for (const population of [POLICY_SCRIPT_GLOBS, POLICY_NESTED_GLOBS]) {
|
|
4343
|
+
const trimmed = {
|
|
4344
|
+
...readConfigRecord(parsed),
|
|
4345
|
+
overrides: overrides.filter((override: unknown) => {
|
|
4346
|
+
const files = readConfigRecord(override).files
|
|
4347
|
+
return !Array.isArray(files) || files.join(' ') !== population.join(' ')
|
|
4348
|
+
}),
|
|
4349
|
+
}
|
|
4350
|
+
expect(
|
|
4351
|
+
inspectPolicyWiring(
|
|
4352
|
+
trimmed,
|
|
4353
|
+
[],
|
|
4354
|
+
[POLICY_PLACEMENT_GLOBS, POLICY_ENDING_GLOBS, POLICY_SCRIPT_GLOBS, POLICY_NESTED_GLOBS],
|
|
4355
|
+
),
|
|
4356
|
+
).toEqual([`no override files list declares the population exactly: ${population.join(' ')}`])
|
|
4357
|
+
}
|
|
4358
|
+
// The control: the script override keeps its exact population while the rule it enables is
|
|
4359
|
+
// set to off, which names the rule as enabled by no record.
|
|
4360
|
+
const disabled = {
|
|
4361
|
+
...readConfigRecord(parsed),
|
|
4362
|
+
overrides: overrides.map((override: unknown) => {
|
|
4363
|
+
const record = readConfigRecord(override)
|
|
4364
|
+
return Object.hasOwn(readConfigRecord(record.rules ?? {}), 'policy/no-malformed-script')
|
|
4365
|
+
? {
|
|
4366
|
+
...record,
|
|
4367
|
+
rules: { ...readConfigRecord(record.rules), 'policy/no-malformed-script': 'off' },
|
|
4368
|
+
}
|
|
4369
|
+
: record
|
|
4370
|
+
}),
|
|
4371
|
+
}
|
|
4372
|
+
expect(
|
|
4373
|
+
inspectPolicyWiring(disabled, ['policy/no-malformed-script'], [POLICY_SCRIPT_GLOBS]),
|
|
4374
|
+
).toEqual(['policy/no-malformed-script is enabled by no top-level or override rules record'])
|
|
3339
4375
|
})
|
|
3340
4376
|
|
|
3341
4377
|
it('loads every configured policy rule through the real binary', () => {
|
|
@@ -3364,6 +4400,15 @@ createSomething({
|
|
|
3364
4400
|
"export const STATUS = 'ready'",
|
|
3365
4401
|
"export const lines = text.trim().split('\\n')",
|
|
3366
4402
|
'export const ending = os.EOL + EOL',
|
|
4403
|
+
'let counter = 0',
|
|
4404
|
+
'counter++',
|
|
4405
|
+
'let unchanged = 1',
|
|
4406
|
+
'var legacy = 1',
|
|
4407
|
+
'function shiftValue(value: number) { value += 1; return value }',
|
|
4408
|
+
'try { void counter } catch (error) { error = undefined; void error }',
|
|
4409
|
+
'void unchanged',
|
|
4410
|
+
'void legacy',
|
|
4411
|
+
'void shiftValue',
|
|
3367
4412
|
'void PrivateMember',
|
|
3368
4413
|
'void ParameterMember',
|
|
3369
4414
|
'void PublicMember',
|
|
@@ -3380,6 +4425,181 @@ createSomething({
|
|
|
3380
4425
|
scratch.write('src/violations/constants.ts', 'export const values = []\n')
|
|
3381
4426
|
scratch.write('src/violations/composables.ts', "export const READY = 'yes'\n")
|
|
3382
4427
|
scratch.write('app/browser/composables/useTheme.ts', 'export function useMode(): void {}\n')
|
|
4428
|
+
scratch.write('app/vue/helpers.ts', "export * from '../print/index.js'\ndebugger\n")
|
|
4429
|
+
scratch.write(
|
|
4430
|
+
'.agents/skills/sample/scripts/shared.ts',
|
|
4431
|
+
["import { readOption } from './helpers.ts'", 'void readOption'].join('\n'),
|
|
4432
|
+
)
|
|
4433
|
+
// The fence reads a dynamic specifier as it reads a static one, and the empty specifier opens
|
|
4434
|
+
// with no `node:` as every other refused specifier does.
|
|
4435
|
+
scratch.write(
|
|
4436
|
+
'.agents/skills/sample/scripts/dynamic.ts',
|
|
4437
|
+
[
|
|
4438
|
+
'/** Loads a sibling. */',
|
|
4439
|
+
"export function load(): Promise<unknown> { return import('./other.ts') }",
|
|
4440
|
+
].join('\n'),
|
|
4441
|
+
)
|
|
4442
|
+
scratch.write(
|
|
4443
|
+
'.agents/skills/sample/scripts/empty.ts',
|
|
4444
|
+
[
|
|
4445
|
+
'/** Loads nothing. */',
|
|
4446
|
+
"export function load(): Promise<unknown> { return import('') }",
|
|
4447
|
+
].join('\n'),
|
|
4448
|
+
)
|
|
4449
|
+
scratch.write(
|
|
4450
|
+
'.agents/skills/sample/scripts/alone.ts',
|
|
4451
|
+
["import { readFileSync } from 'node:fs'", 'void readFileSync'].join('\n'),
|
|
4452
|
+
)
|
|
4453
|
+
// A script that hides a declaration, exports one with no doc block, and runs at import draws
|
|
4454
|
+
// every message; the conforming script beside it draws none, and its `debugger` statement
|
|
4455
|
+
// sits inside a function, outside the top-level statements the script rule reads, so the
|
|
4456
|
+
// root `no-debugger` rule proves the file entered the run.
|
|
4457
|
+
scratch.write(
|
|
4458
|
+
'.agents/skills/sample/scripts/malformed.ts',
|
|
4459
|
+
[
|
|
4460
|
+
'function hidden(): void {}',
|
|
4461
|
+
'export function bare(): void {}',
|
|
4462
|
+
'hidden()',
|
|
4463
|
+
'bare()',
|
|
4464
|
+
].join('\n'),
|
|
4465
|
+
)
|
|
4466
|
+
scratch.write(
|
|
4467
|
+
'.agents/skills/sample/scripts/conforming.ts',
|
|
4468
|
+
[
|
|
4469
|
+
"import { realpathSync } from 'node:fs'",
|
|
4470
|
+
'',
|
|
4471
|
+
'/** Reads the one value. */',
|
|
4472
|
+
'export function read(): number {',
|
|
4473
|
+
'\tdebugger',
|
|
4474
|
+
'\treturn 1',
|
|
4475
|
+
'}',
|
|
4476
|
+
'',
|
|
4477
|
+
'/** Runs the script. */',
|
|
4478
|
+
'export function main(): number {',
|
|
4479
|
+
'\treturn read()',
|
|
4480
|
+
'}',
|
|
4481
|
+
'',
|
|
4482
|
+
'if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {',
|
|
4483
|
+
'\tprocess.exitCode = main()',
|
|
4484
|
+
'}',
|
|
4485
|
+
].join('\n'),
|
|
4486
|
+
)
|
|
4487
|
+
// Every form of write to a parameter or a catch binding draws the built-in rule, one file per
|
|
4488
|
+
// form so that a form the binary misses names itself.
|
|
4489
|
+
const reassignments = [
|
|
4490
|
+
{
|
|
4491
|
+
file: 'param-update',
|
|
4492
|
+
code: 'no-param-reassign',
|
|
4493
|
+
source: 'export function f(a: number) { a++; return a }',
|
|
4494
|
+
},
|
|
4495
|
+
{
|
|
4496
|
+
file: 'param-array-target',
|
|
4497
|
+
code: 'no-param-reassign',
|
|
4498
|
+
source: 'export function f(b: number[]) { ;[b] = [[1]]; return b }',
|
|
4499
|
+
},
|
|
4500
|
+
{
|
|
4501
|
+
file: 'param-object-target',
|
|
4502
|
+
code: 'no-param-reassign',
|
|
4503
|
+
source: 'export function f(c: object) { ;({ c } = { c: {} }); return c }',
|
|
4504
|
+
},
|
|
4505
|
+
{
|
|
4506
|
+
file: 'param-for-in-head',
|
|
4507
|
+
code: 'no-param-reassign',
|
|
4508
|
+
source: 'export function f(e: string, c: object) { for (e in c) void e; return e }',
|
|
4509
|
+
},
|
|
4510
|
+
{
|
|
4511
|
+
file: 'param-for-of-head',
|
|
4512
|
+
code: 'no-param-reassign',
|
|
4513
|
+
source: "export function f(e: string) { for (e of ['x']) void e; return e }",
|
|
4514
|
+
},
|
|
4515
|
+
{
|
|
4516
|
+
file: 'catch-update',
|
|
4517
|
+
code: 'no-ex-assign',
|
|
4518
|
+
source: 'try { void 0 } catch (err) { err++ }',
|
|
4519
|
+
},
|
|
4520
|
+
{
|
|
4521
|
+
file: 'catch-destructured',
|
|
4522
|
+
code: 'no-ex-assign',
|
|
4523
|
+
source: "try { void 0 } catch ({ message }) { message = 'x' }",
|
|
4524
|
+
},
|
|
4525
|
+
{
|
|
4526
|
+
file: 'catch-for-of-head',
|
|
4527
|
+
code: 'no-ex-assign',
|
|
4528
|
+
source: 'try { void 0 } catch (err) { for (err of [1]) void err }',
|
|
4529
|
+
},
|
|
4530
|
+
].map(({ file, code, source }) => ({
|
|
4531
|
+
code: `eslint(${code})`,
|
|
4532
|
+
filename: `src/violations/reassign/${file}.ts`,
|
|
4533
|
+
source,
|
|
4534
|
+
}))
|
|
4535
|
+
for (const { filename, source } of reassignments) scratch.write(filename, source)
|
|
4536
|
+
// An ambient block holds a `let` with no value and no `declare` flag of its own, and the
|
|
4537
|
+
// `debugger` statement proves the file entered the run the absence assertion reads.
|
|
4538
|
+
scratch.write(
|
|
4539
|
+
'src/violations/ambient.ts',
|
|
4540
|
+
[
|
|
4541
|
+
'declare global { let ambientGlobal: number }',
|
|
4542
|
+
"declare module 'ambient-module' { export let ambientExport: string }",
|
|
4543
|
+
'debugger',
|
|
4544
|
+
'export {}',
|
|
4545
|
+
].join('\n'),
|
|
4546
|
+
)
|
|
4547
|
+
scratch.write(
|
|
4548
|
+
'src/violations/types.ts',
|
|
4549
|
+
[
|
|
4550
|
+
'/** Describes a mutable record. */',
|
|
4551
|
+
'export interface MutableRecord { id: string }',
|
|
4552
|
+
'/** Describes a frozen record. */',
|
|
4553
|
+
'export interface FrozenRecord { readonly id: string }',
|
|
4554
|
+
'/** Aliases an object type. */',
|
|
4555
|
+
'export type MutableAlias = { value: number }',
|
|
4556
|
+
'/** Reads one field through an inline parameter type. */',
|
|
4557
|
+
'export type Reader = (options: { id: string }) => string',
|
|
4558
|
+
].join('\n'),
|
|
4559
|
+
)
|
|
4560
|
+
// The setup-module fences replace the generic tests restriction for the files they name,
|
|
4561
|
+
// and the proofs beside them keep the generic one. The `debugger` statement proves each file
|
|
4562
|
+
// entered the run.
|
|
4563
|
+
scratch.write(
|
|
4564
|
+
'tests/setup.ts',
|
|
4565
|
+
["import { readFileSync } from 'node:fs'", "import { ref } from 'vue'", 'debugger'].join(
|
|
4566
|
+
'\n',
|
|
4567
|
+
),
|
|
4568
|
+
)
|
|
4569
|
+
scratch.write(
|
|
4570
|
+
'tests/setupBrowser.ts',
|
|
4571
|
+
[
|
|
4572
|
+
"import { describe, it } from 'vitest'",
|
|
4573
|
+
"import { readFileSync } from 'node:fs'",
|
|
4574
|
+
'debugger',
|
|
4575
|
+
].join('\n'),
|
|
4576
|
+
)
|
|
4577
|
+
scratch.write(
|
|
4578
|
+
'tests/setupServer.ts',
|
|
4579
|
+
["import { expect } from 'vitest'", 'debugger'].join('\n'),
|
|
4580
|
+
)
|
|
4581
|
+
scratch.write(
|
|
4582
|
+
'tests/setupPolicy.ts',
|
|
4583
|
+
["import { readFileSync } from 'node:fs'", "import { vi } from 'vitest'", 'debugger'].join(
|
|
4584
|
+
'\n',
|
|
4585
|
+
),
|
|
4586
|
+
)
|
|
4587
|
+
scratch.write(
|
|
4588
|
+
'tests/setupStyles.ts',
|
|
4589
|
+
["import { TestRunner } from 'vitest'", 'debugger'].join('\n'),
|
|
4590
|
+
)
|
|
4591
|
+
scratch.write(
|
|
4592
|
+
'tests/setup.test.ts',
|
|
4593
|
+
["import { describe, expect, it } from 'vitest'", 'debugger'].join('\n'),
|
|
4594
|
+
)
|
|
4595
|
+
scratch.write(
|
|
4596
|
+
'tests/titles.test.ts',
|
|
4597
|
+
[
|
|
4598
|
+
"import { expect, it } from 'vitest'",
|
|
4599
|
+
"it('reads one value', () => expect(1).toBe(1))",
|
|
4600
|
+
"it('reads one value', () => expect(2).toBe(2))",
|
|
4601
|
+
].join('\n'),
|
|
4602
|
+
)
|
|
3383
4603
|
// The line-ending population reaches src, app, and configs alone, so this module
|
|
3384
4604
|
// outside them carries the same defect and must draw no diagnostic. The `debugger`
|
|
3385
4605
|
// statement is the arrival control the root `no-debugger` rule reports, which proves
|
|
@@ -3432,7 +4652,18 @@ createSomething({
|
|
|
3432
4652
|
const config = resolve(scratch.path, '.oxlintrc.json')
|
|
3433
4653
|
const violations = spawnSync(
|
|
3434
4654
|
process.execPath,
|
|
3435
|
-
[
|
|
4655
|
+
[
|
|
4656
|
+
binary,
|
|
4657
|
+
'--config',
|
|
4658
|
+
config,
|
|
4659
|
+
'--format',
|
|
4660
|
+
'json',
|
|
4661
|
+
'src/violations',
|
|
4662
|
+
'app',
|
|
4663
|
+
'scripts',
|
|
4664
|
+
'tests',
|
|
4665
|
+
'.agents',
|
|
4666
|
+
],
|
|
3436
4667
|
{ cwd: scratch.path, encoding: 'utf8', timeout: 15_000 },
|
|
3437
4668
|
)
|
|
3438
4669
|
const clean = spawnSync(
|
|
@@ -3470,7 +4701,33 @@ createSomething({
|
|
|
3470
4701
|
for (const { code, filename } of [
|
|
3471
4702
|
{ code: 'policy(no-mocking)', filename: 'src/violations/fixture.ts' },
|
|
3472
4703
|
{ code: 'policy(no-keyword-privacy)', filename: 'src/violations/fixture.ts' },
|
|
4704
|
+
{ code: 'policy(no-let)', filename: 'src/violations/fixture.ts' },
|
|
4705
|
+
{ code: 'eslint(prefer-const)', filename: 'src/violations/fixture.ts' },
|
|
4706
|
+
{ code: 'eslint(no-var)', filename: 'src/violations/fixture.ts' },
|
|
4707
|
+
{ code: 'eslint(no-param-reassign)', filename: 'src/violations/fixture.ts' },
|
|
4708
|
+
{ code: 'eslint(no-ex-assign)', filename: 'src/violations/fixture.ts' },
|
|
4709
|
+
{ code: 'vitest(no-identical-title)', filename: 'tests/titles.test.ts' },
|
|
4710
|
+
{
|
|
4711
|
+
code: 'eslint(no-restricted-imports)',
|
|
4712
|
+
filename: '.agents/skills/sample/scripts/shared.ts',
|
|
4713
|
+
},
|
|
4714
|
+
{
|
|
4715
|
+
code: 'eslint(no-restricted-imports)',
|
|
4716
|
+
filename: '.agents/skills/sample/scripts/dynamic.ts',
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
code: 'eslint(no-restricted-imports)',
|
|
4720
|
+
filename: '.agents/skills/sample/scripts/empty.ts',
|
|
4721
|
+
},
|
|
3473
4722
|
{ code: 'policy(no-nested-functions)', filename: 'src/violations/fixture.ts' },
|
|
4723
|
+
{
|
|
4724
|
+
code: 'policy(no-malformed-script)',
|
|
4725
|
+
filename: '.agents/skills/sample/scripts/malformed.ts',
|
|
4726
|
+
},
|
|
4727
|
+
{ code: 'policy(no-mutable-property)', filename: 'src/violations/types.ts' },
|
|
4728
|
+
{ code: 'eslint(no-restricted-imports)', filename: 'tests/setup.ts' },
|
|
4729
|
+
{ code: 'eslint(no-restricted-imports)', filename: 'tests/setupBrowser.ts' },
|
|
4730
|
+
{ code: 'eslint(no-restricted-imports)', filename: 'tests/setupServer.ts' },
|
|
3474
4731
|
{ code: 'policy(no-misplaced-type)', filename: 'src/violations/fixture.ts' },
|
|
3475
4732
|
{ code: 'policy(no-misplaced-data)', filename: 'src/violations/fixture.ts' },
|
|
3476
4733
|
{ code: 'policy(no-misplaced-function)', filename: 'src/violations/fixture.ts' },
|
|
@@ -3482,6 +4739,8 @@ createSomething({
|
|
|
3482
4739
|
{ code: 'policy(no-misnamed-plugin)', filename: 'src/violations/plugins.ts' },
|
|
3483
4740
|
{ code: 'policy(no-malformed-constant)', filename: 'src/violations/constants.ts' },
|
|
3484
4741
|
{ code: 'policy(no-malformed-domain)', filename: 'src/violations/composables.ts' },
|
|
4742
|
+
{ code: 'policy(no-cross-vue-face)', filename: 'app/vue/helpers.ts' },
|
|
4743
|
+
{ code: 'eslint(no-debugger)', filename: 'app/vue/helpers.ts' },
|
|
3485
4744
|
{
|
|
3486
4745
|
code: 'policy(no-malformed-domain)',
|
|
3487
4746
|
filename: 'app/browser/composables/useTheme.ts',
|
|
@@ -3498,12 +4757,55 @@ createSomething({
|
|
|
3498
4757
|
`${code} ${normalizePolicyFilename(comparisonRoot, filename)}`,
|
|
3499
4758
|
)
|
|
3500
4759
|
}
|
|
4760
|
+
for (const { code, filename } of reassignments) {
|
|
4761
|
+
expect(violationCodes).toContain(
|
|
4762
|
+
`${code} ${normalizePolicyFilename(comparisonRoot, filename)}`,
|
|
4763
|
+
)
|
|
4764
|
+
}
|
|
4765
|
+
expect(violationCodes).toContain(
|
|
4766
|
+
`eslint(no-debugger) ${normalizePolicyFilename(comparisonRoot, 'src/violations/ambient.ts')}`,
|
|
4767
|
+
)
|
|
4768
|
+
expect(violationCodes).not.toContain(
|
|
4769
|
+
`policy(no-let) ${normalizePolicyFilename(comparisonRoot, 'src/violations/ambient.ts')}`,
|
|
4770
|
+
)
|
|
3501
4771
|
expect(violationCodes).toContain(
|
|
3502
4772
|
`eslint(no-debugger) ${normalizePolicyFilename(comparisonRoot, 'scripts/read.ts')}`,
|
|
3503
4773
|
)
|
|
3504
4774
|
expect(violationCodes).not.toContain(
|
|
3505
4775
|
`policy(no-host-line-endings) ${normalizePolicyFilename(comparisonRoot, 'scripts/read.ts')}`,
|
|
3506
4776
|
)
|
|
4777
|
+
expect(violationCodes).not.toContain(
|
|
4778
|
+
`eslint(no-restricted-imports) ${normalizePolicyFilename(comparisonRoot, '.agents/skills/sample/scripts/alone.ts')}`,
|
|
4779
|
+
)
|
|
4780
|
+
// The conforming script arrives, through the `debugger` statement inside its function, and
|
|
4781
|
+
// draws no script diagnostic; the inline parameter type beside the two mutable properties
|
|
4782
|
+
// draws none either, so the types file draws exactly two.
|
|
4783
|
+
expect(violationCodes).toContain(
|
|
4784
|
+
`eslint(no-debugger) ${normalizePolicyFilename(comparisonRoot, '.agents/skills/sample/scripts/conforming.ts')}`,
|
|
4785
|
+
)
|
|
4786
|
+
expect(violationCodes).not.toContain(
|
|
4787
|
+
`policy(no-malformed-script) ${normalizePolicyFilename(comparisonRoot, '.agents/skills/sample/scripts/conforming.ts')}`,
|
|
4788
|
+
)
|
|
4789
|
+
expect(
|
|
4790
|
+
violationCodes.filter(
|
|
4791
|
+
(code) =>
|
|
4792
|
+
code ===
|
|
4793
|
+
`policy(no-mutable-property) ${normalizePolicyFilename(comparisonRoot, 'src/violations/types.ts')}`,
|
|
4794
|
+
),
|
|
4795
|
+
).toHaveLength(2)
|
|
4796
|
+
// The fences replace only the restriction of the files they name: a setup module that
|
|
4797
|
+
// imports a Node module or a vitest helper the law admits, a browser-enabled helper module
|
|
4798
|
+
// the law admits, and a setup proof that registers tests all draw the root arrival
|
|
4799
|
+
// diagnostic and no import restriction.
|
|
4800
|
+
for (const filename of [
|
|
4801
|
+
'tests/setupPolicy.ts',
|
|
4802
|
+
'tests/setupStyles.ts',
|
|
4803
|
+
'tests/setup.test.ts',
|
|
4804
|
+
]) {
|
|
4805
|
+
const normalized = normalizePolicyFilename(comparisonRoot, filename)
|
|
4806
|
+
expect(violationCodes).toContain(`eslint(no-debugger) ${normalized}`)
|
|
4807
|
+
expect(violationCodes).not.toContain(`eslint(no-restricted-imports) ${normalized}`)
|
|
4808
|
+
}
|
|
3507
4809
|
expect(clean.status).toBe(0)
|
|
3508
4810
|
expect(cleanCodes).toHaveLength(0)
|
|
3509
4811
|
} finally {
|
|
@@ -3673,6 +4975,7 @@ describe('configuration helpers', () => {
|
|
|
3673
4975
|
'readBoundedFile',
|
|
3674
4976
|
'readHandleBytes',
|
|
3675
4977
|
'readCompilerOutput',
|
|
4978
|
+
'resolveCompiler',
|
|
3676
4979
|
'rewriteCoreSpecifier',
|
|
3677
4980
|
'sourceFallback',
|
|
3678
4981
|
'workspacePath',
|
|
@@ -3780,7 +5083,7 @@ describe('configuration helpers', () => {
|
|
|
3780
5083
|
const scratch = createPolicyScratch({ prefix: 'orkestrel-config-handle-' })
|
|
3781
5084
|
try {
|
|
3782
5085
|
// Each case is a file's text, the offset already read into the buffer, and the largest total allowed.
|
|
3783
|
-
const
|
|
5086
|
+
const results = [
|
|
3784
5087
|
{ text: 'abc', offset: 0, limit: 4 },
|
|
3785
5088
|
{ text: 'abcd', offset: 0, limit: 4 },
|
|
3786
5089
|
{ text: 'abcde', offset: 0, limit: 4 },
|
|
@@ -3800,8 +5103,8 @@ describe('configuration helpers', () => {
|
|
|
3800
5103
|
})
|
|
3801
5104
|
// A file at or under the limit answers its length; the shifted read adds the offset to the
|
|
3802
5105
|
// length and fills the buffer past it; a file over the limit answers nothing.
|
|
3803
|
-
expect(
|
|
3804
|
-
expect(
|
|
5106
|
+
expect(results.map(({ count }) => count)).toStrictEqual([3, 4, undefined, 0, 5, undefined])
|
|
5107
|
+
expect(results.map(({ filled }) => filled)).toStrictEqual(['abc', 'abcd', '', '', 'abc', ''])
|
|
3805
5108
|
} finally {
|
|
3806
5109
|
scratch.destroy()
|
|
3807
5110
|
}
|
|
@@ -3854,18 +5157,18 @@ describe('configuration helpers', () => {
|
|
|
3854
5157
|
logLevel: 'silent',
|
|
3855
5158
|
build: { outDir: 'out', lib: { entry, formats: ['es'] } },
|
|
3856
5159
|
}
|
|
3857
|
-
const
|
|
3858
|
-
|
|
3859
|
-
expect(
|
|
3860
|
-
expect(
|
|
3861
|
-
expect(
|
|
3862
|
-
expect(
|
|
5160
|
+
const buildState = new configHelpers.BuildState()
|
|
5161
|
+
buildState.resolve(await resolveConfig(inline, 'build'))
|
|
5162
|
+
expect(buildState.building).toBe(true)
|
|
5163
|
+
expect(buildState.output).toBe(resolve(scratch.path, 'out'))
|
|
5164
|
+
expect(buildState.root).toBe(realpathSync.native(scratch.path))
|
|
5165
|
+
expect(buildState.source).toBe(entry)
|
|
3863
5166
|
|
|
3864
5167
|
// The control: a serve command is not a build, and a config with no library entry has no source.
|
|
3865
|
-
const
|
|
3866
|
-
|
|
3867
|
-
expect(
|
|
3868
|
-
expect(
|
|
5168
|
+
const serveState = new configHelpers.BuildState()
|
|
5169
|
+
serveState.resolve(await resolveConfig({ ...inline, build: { outDir: 'out' } }, 'serve'))
|
|
5170
|
+
expect(serveState.building).toBe(false)
|
|
5171
|
+
expect(serveState.source).toBe('')
|
|
3869
5172
|
} finally {
|
|
3870
5173
|
scratch.destroy()
|
|
3871
5174
|
}
|
|
@@ -4060,6 +5363,65 @@ describe('configuration helpers', () => {
|
|
|
4060
5363
|
}
|
|
4061
5364
|
})
|
|
4062
5365
|
|
|
5366
|
+
it('resolves the installed compiler through bin.tsc when private package paths are unexported', () => {
|
|
5367
|
+
const compiler = configHelpers.resolveCompiler(import.meta.url)
|
|
5368
|
+
expect(configHelpers.readCompilerOutput(compiler, ['--version'])).toMatch(/^Version \d+\./u)
|
|
5369
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-compiler-exports-' })
|
|
5370
|
+
try {
|
|
5371
|
+
const root = join(scratch.path, 'node_modules/typescript')
|
|
5372
|
+
scratch.write('package.json', '{"type":"module"}')
|
|
5373
|
+
scratch.write(
|
|
5374
|
+
'node_modules/typescript/package.json',
|
|
5375
|
+
JSON.stringify({
|
|
5376
|
+
name: 'typescript',
|
|
5377
|
+
exports: { './package.json': './package.json' },
|
|
5378
|
+
bin: { tsc: relative(root, compiler) },
|
|
5379
|
+
}),
|
|
5380
|
+
)
|
|
5381
|
+
const path = join(scratch.path, 'package.json')
|
|
5382
|
+
expect(() => createRequire(path).resolve('typescript/bin/tsc')).toThrow(
|
|
5383
|
+
'not defined by "exports"',
|
|
5384
|
+
)
|
|
5385
|
+
expect(configHelpers.resolveCompiler(path)).toBe(compiler)
|
|
5386
|
+
expect(
|
|
5387
|
+
configHelpers.readCompilerOutput(configHelpers.resolveCompiler(path), ['--version']),
|
|
5388
|
+
).toMatch(/^Version \d+\./u)
|
|
5389
|
+
scratch.write('consumer.ts', 'const value: string = 1\nconsole.log(value)\n')
|
|
5390
|
+
const refused = spawnSync(
|
|
5391
|
+
process.execPath,
|
|
5392
|
+
[compiler, '--noEmit', '--pretty', 'false', '--skipLibCheck', 'consumer.ts'],
|
|
5393
|
+
{ cwd: scratch.path, encoding: 'utf8', windowsHide: true },
|
|
5394
|
+
)
|
|
5395
|
+
expect(refused.status).toBeGreaterThan(0)
|
|
5396
|
+
expect(refused.stdout).toContain('error TS2322')
|
|
5397
|
+
scratch.write('consumer.ts', "const value: string = 'accepted'\nconsole.log(value)\n")
|
|
5398
|
+
const accepted = spawnSync(
|
|
5399
|
+
process.execPath,
|
|
5400
|
+
[compiler, '--noEmit', '--pretty', 'false', '--skipLibCheck', 'consumer.ts'],
|
|
5401
|
+
{ cwd: scratch.path, encoding: 'utf8', windowsHide: true },
|
|
5402
|
+
)
|
|
5403
|
+
expect(accepted.status, `${accepted.stdout}${accepted.stderr}`).toBe(0)
|
|
5404
|
+
} finally {
|
|
5405
|
+
scratch.destroy()
|
|
5406
|
+
}
|
|
5407
|
+
})
|
|
5408
|
+
|
|
5409
|
+
it.each([{}, { bin: null }, { bin: { tsc: 7 } }])(
|
|
5410
|
+
'refuses a compiler manifest without a bin.tsc command: %s',
|
|
5411
|
+
(declared) => {
|
|
5412
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-compiler-manifest-' })
|
|
5413
|
+
try {
|
|
5414
|
+
scratch.write('package.json', '{"type":"module"}')
|
|
5415
|
+
scratch.write('node_modules/typescript/package.json', JSON.stringify(declared))
|
|
5416
|
+
expect(() => configHelpers.resolveCompiler(join(scratch.path, 'package.json'))).toThrow(
|
|
5417
|
+
'The TypeScript package declares no bin.tsc command',
|
|
5418
|
+
)
|
|
5419
|
+
} finally {
|
|
5420
|
+
scratch.destroy()
|
|
5421
|
+
}
|
|
5422
|
+
},
|
|
5423
|
+
)
|
|
5424
|
+
|
|
4063
5425
|
// The scope reading applies only where the workspace publishes source from `src`, which is what
|
|
4064
5426
|
// `publishes` reads, and the name ends at that mechanism. An absent face project is the defect
|
|
4065
5427
|
// this case exists to report rather than a second reason to excuse it, so a workspace holding
|
|
@@ -4071,7 +5433,7 @@ describe('configuration helpers', () => {
|
|
|
4071
5433
|
it.skipIf(!POLICY_PUBLISHES)(
|
|
4072
5434
|
'reads the compiler scope a declaration roll-up requires [inapplicable where src holds no recognized environment directory]',
|
|
4073
5435
|
() => {
|
|
4074
|
-
const compiler =
|
|
5436
|
+
const compiler = configHelpers.resolveCompiler(import.meta.url)
|
|
4075
5437
|
// The order mirrors ENVIRONMENTS in src/core/constants.ts; a server-only workspace vendors
|
|
4076
5438
|
// no core project, so this walks to the first face the workspace actually carries.
|
|
4077
5439
|
const faces = ['core', 'browser', 'server']
|
|
@@ -4131,7 +5493,7 @@ describe('configuration helpers', () => {
|
|
|
4131
5493
|
// reading a face project must not excuse it from the helpers behind the roll-up, which it
|
|
4132
5494
|
// vendors whether or not it publishes.
|
|
4133
5495
|
it('reads the refusals, guards, overrides, and rewrites a declaration roll-up requires from every workspace', () => {
|
|
4134
|
-
const compiler =
|
|
5496
|
+
const compiler = configHelpers.resolveCompiler(import.meta.url)
|
|
4135
5497
|
// Any project path resolves these, because each reading is refused by the text before the
|
|
4136
5498
|
// path is read. The root project is the one file every workspace carries.
|
|
4137
5499
|
const project = resolve(POLICY_ROOT, 'tsconfig.json')
|
|
@@ -4295,8 +5657,15 @@ describe('configuration helpers', () => {
|
|
|
4295
5657
|
const rewritten = join(workspace, 'rewritten')
|
|
4296
5658
|
const kept = join(workspace, 'kept')
|
|
4297
5659
|
const builds = [
|
|
4298
|
-
{
|
|
4299
|
-
|
|
5660
|
+
{
|
|
5661
|
+
output: rewritten,
|
|
5662
|
+
plugin: configHelpers.declarationRollup({
|
|
5663
|
+
project,
|
|
5664
|
+
rewrite: (text) =>
|
|
5665
|
+
configHelpers.rewriteBrowserSpecifier(configHelpers.rewriteCoreSpecifier(text)),
|
|
5666
|
+
}),
|
|
5667
|
+
},
|
|
5668
|
+
{ output: kept, plugin: configHelpers.declarationRollup({ project }) },
|
|
4300
5669
|
]
|
|
4301
5670
|
for (const face of builds) {
|
|
4302
5671
|
await build({
|
|
@@ -4304,19 +5673,7 @@ describe('configuration helpers', () => {
|
|
|
4304
5673
|
configFile: false,
|
|
4305
5674
|
logLevel: 'silent',
|
|
4306
5675
|
publicDir: false,
|
|
4307
|
-
plugins: [
|
|
4308
|
-
configHelpers.declarationRollup(
|
|
4309
|
-
face.rewrite
|
|
4310
|
-
? {
|
|
4311
|
-
project,
|
|
4312
|
-
rewrite: (text) =>
|
|
4313
|
-
configHelpers.rewriteBrowserSpecifier(
|
|
4314
|
-
configHelpers.rewriteCoreSpecifier(text),
|
|
4315
|
-
),
|
|
4316
|
-
}
|
|
4317
|
-
: { project },
|
|
4318
|
-
),
|
|
4319
|
-
],
|
|
5676
|
+
plugins: [face.plugin],
|
|
4320
5677
|
build: {
|
|
4321
5678
|
write: true,
|
|
4322
5679
|
outDir: face.output,
|
|
@@ -4331,12 +5688,12 @@ describe('configuration helpers', () => {
|
|
|
4331
5688
|
}
|
|
4332
5689
|
|
|
4333
5690
|
const idle = join(workspace, 'idle')
|
|
4334
|
-
const
|
|
5691
|
+
const plugins = configHelpers.declarationRollup({
|
|
4335
5692
|
project,
|
|
4336
5693
|
rewrite: configHelpers.rewriteCoreSpecifier,
|
|
4337
5694
|
})
|
|
4338
|
-
const configure =
|
|
4339
|
-
const close =
|
|
5695
|
+
const configure = plugins.configResolved
|
|
5696
|
+
const close = plugins.closeBundle
|
|
4340
5697
|
if (typeof configure !== 'function' || typeof close !== 'function') {
|
|
4341
5698
|
throw new Error('The declaration roll-up exposes no build hooks')
|
|
4342
5699
|
}
|
|
@@ -4383,28 +5740,7 @@ describe('configuration helpers', () => {
|
|
|
4383
5740
|
// list reads every entry first. Each case runs a policy reader over a recorded list and pins the
|
|
4384
5741
|
// outcome and the entries it read, as the loop the reader replaced gave them.
|
|
4385
5742
|
describe('policy readers consume a caller-supplied list one entry at a time', () => {
|
|
4386
|
-
const
|
|
4387
|
-
const declarator = (): PolicyExpression => ({
|
|
4388
|
-
type: 'VariableDeclarator',
|
|
4389
|
-
range: [0, 0],
|
|
4390
|
-
id: name,
|
|
4391
|
-
init: { type: 'ArrowFunctionExpression', range: [0, 0] },
|
|
4392
|
-
})
|
|
4393
|
-
const exported = (): PolicyExpression => ({
|
|
4394
|
-
type: 'ExportNamedDeclaration',
|
|
4395
|
-
range: [47, 63],
|
|
4396
|
-
declaration: { type: 'FunctionDeclaration', range: [0, 0], id: name },
|
|
4397
|
-
})
|
|
4398
|
-
const comment = (): PolicyComment => ({ type: 'Block', value: '* One.', range: [0, 11] })
|
|
4399
|
-
// A revoked proxy refuses every read, which fails the reader once it holds the entry.
|
|
4400
|
-
const refused =
|
|
4401
|
-
<T extends object>(entry: () => T) =>
|
|
4402
|
-
(): T => {
|
|
4403
|
-
const { proxy, revoke } = Proxy.revocable(entry(), {})
|
|
4404
|
-
revoke()
|
|
4405
|
-
return proxy
|
|
4406
|
-
}
|
|
4407
|
-
const throwing = [
|
|
5743
|
+
const failureCases = [
|
|
4408
5744
|
[POLICY_LAZY_SHAPES.hole, 'throw TypeError', []],
|
|
4409
5745
|
[POLICY_LAZY_SHAPES.holeFailure, 'throw TypeError', []],
|
|
4410
5746
|
[POLICY_LAZY_SHAPES.failureFirst, 'throw RangeError', [0]],
|
|
@@ -4420,11 +5756,11 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4420
5756
|
statementToPolicyBindings({ type: 'VariableDeclaration', range: [0, 0], declarations }).map(
|
|
4421
5757
|
(binding) => binding.name,
|
|
4422
5758
|
),
|
|
4423
|
-
{ valid:
|
|
5759
|
+
{ valid: createPolicyDeclarator, refusal: createPolicyRefused(createPolicyDeclarator) },
|
|
4424
5760
|
[
|
|
4425
5761
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
4426
5762
|
[POLICY_LAZY_SHAPES.dense, 'return ["run"]', [0]],
|
|
4427
|
-
...
|
|
5763
|
+
...failureCases,
|
|
4428
5764
|
],
|
|
4429
5765
|
),
|
|
4430
5766
|
...createPolicyLazyCases(
|
|
@@ -4435,7 +5771,7 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4435
5771
|
{
|
|
4436
5772
|
filename: '/ws/src/server/execution/run.ts',
|
|
4437
5773
|
cwd: '/ws',
|
|
4438
|
-
sourceCode: { text: '', getAllComments: () => [] },
|
|
5774
|
+
sourceCode: { text: '', getAllComments: () => [], getDeclaredVariables: () => [] },
|
|
4439
5775
|
report: (diagnostic) => {
|
|
4440
5776
|
reports.push(diagnostic.messageId)
|
|
4441
5777
|
},
|
|
@@ -4444,11 +5780,11 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4444
5780
|
)
|
|
4445
5781
|
return reports
|
|
4446
5782
|
},
|
|
4447
|
-
{ valid:
|
|
5783
|
+
{ valid: createPolicyExport, refusal: createPolicyRefused(createPolicyExport) },
|
|
4448
5784
|
[
|
|
4449
5785
|
[POLICY_LAZY_SHAPES.empty, 'return ["module"]', []],
|
|
4450
5786
|
[POLICY_LAZY_SHAPES.dense, 'return []', [0]],
|
|
4451
|
-
...
|
|
5787
|
+
...failureCases,
|
|
4452
5788
|
],
|
|
4453
5789
|
),
|
|
4454
5790
|
...createPolicyLazyCases(
|
|
@@ -4456,27 +5792,30 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4456
5792
|
(body: PolicyExpression[]) =>
|
|
4457
5793
|
programToPolicyDocs(
|
|
4458
5794
|
{ type: 'Program', range: [0, 0], body },
|
|
4459
|
-
{
|
|
5795
|
+
{
|
|
5796
|
+
text: '/** One. */\nexport function run() {}',
|
|
5797
|
+
getAllComments: () => [createPolicyComment()],
|
|
5798
|
+
},
|
|
4460
5799
|
).map((doc) => doc.name),
|
|
4461
|
-
{ valid:
|
|
5800
|
+
{ valid: createPolicyExport, refusal: createPolicyRefused(createPolicyExport) },
|
|
4462
5801
|
[
|
|
4463
5802
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
4464
5803
|
[POLICY_LAZY_SHAPES.dense, 'return []', [0]],
|
|
4465
|
-
...
|
|
5804
|
+
...failureCases,
|
|
4466
5805
|
],
|
|
4467
5806
|
),
|
|
4468
5807
|
...createPolicyLazyCases(
|
|
4469
5808
|
'programToPolicyDocs comments',
|
|
4470
5809
|
(comments: PolicyComment[]) =>
|
|
4471
5810
|
programToPolicyDocs(
|
|
4472
|
-
{ type: 'Program', range: [0, 0], body: [
|
|
5811
|
+
{ type: 'Program', range: [0, 0], body: [createPolicyExport()] },
|
|
4473
5812
|
{ text: '/** One. */\n', getAllComments: () => comments },
|
|
4474
5813
|
).map((doc) => doc.name),
|
|
4475
|
-
{ valid:
|
|
5814
|
+
{ valid: createPolicyComment, refusal: createPolicyRefused(createPolicyComment) },
|
|
4476
5815
|
[
|
|
4477
5816
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
4478
5817
|
[POLICY_LAZY_SHAPES.dense, 'return ["run"]', [0]],
|
|
4479
|
-
...
|
|
5818
|
+
...failureCases,
|
|
4480
5819
|
],
|
|
4481
5820
|
),
|
|
4482
5821
|
...createPolicyLazyCases(
|
|
@@ -4486,7 +5825,7 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4486
5825
|
{ type: 'Program', range: [0, 0], body: [{ type: 'ImportDeclaration', range: [0, 0] }] },
|
|
4487
5826
|
{ text: 'import x', getAllComments: () => comments },
|
|
4488
5827
|
).map((doc) => doc.name),
|
|
4489
|
-
{ valid:
|
|
5828
|
+
{ valid: createPolicyComment, refusal: createPolicyRefused(createPolicyComment) },
|
|
4490
5829
|
[
|
|
4491
5830
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
4492
5831
|
[POLICY_LAZY_SHAPES.dense, 'return []', []],
|