@starci/eslint-grammar-core 0.15.34 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/why.js +15 -0
- package/dist/mascot.d.ts +28 -0
- package/dist/mascot.js +166 -0
- package/dist/surface-depth.d.ts +7 -0
- package/dist/surface-depth.js +92 -0
- package/docs/mascot.md +60 -0
- package/docs/surface-depth.md +40 -0
- package/package.json +2 -2
- package/runtime/bundle.json +74 -0
- package/runtime/engine/by-code-unit.mjs +6 -6
- package/runtime/engine/model-config.mjs +98 -76
- package/runtime/engine/orca-config.mjs +22 -17
- package/runtime/engine/release-config.mjs +22 -18
- package/runtime/engine/removed-vocabulary.mjs +21 -20
- package/runtime/engine/resources-config.mjs +42 -35
- package/runtime/engine/runtime-root.mjs +11 -13
- package/runtime/engine/secrets.mjs +170 -102
- package/runtime/engine/sonar-config.mjs +16 -13
- package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
- package/runtime/knowledge/hfs/rules.yaml +95 -6
- package/runtime/knowledge/hfs/slots.yaml +2 -2
- package/runtime/knowledge/patterns/fe/ui.yaml +13 -1
- package/runtime/scripts/hfs/allows.mjs +37 -38
- package/runtime/scripts/hfs/architecture/config.mjs +302 -268
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +2 -5
- package/runtime/scripts/hfs/architecture/type-context.mjs +255 -250
- package/runtime/scripts/hfs/architecture/typescript.mjs +358 -318
- package/runtime/scripts/hfs/declaration-shape.mjs +140 -103
- package/runtime/scripts/hfs/declaration-slots.mjs +64 -61
- package/runtime/scripts/hfs/edition-slots.mjs +43 -40
- package/runtime/scripts/hfs/param-names.mjs +6 -11
- package/runtime/scripts/hfs/rule-catalog.mjs +353 -257
- package/runtime/scripts/hfs/rule-params-shape.mjs +60 -47
- package/runtime/scripts/hfs/slot-app-view.mjs +69 -74
- package/runtime/scripts/hfs/slot-classify.mjs +42 -38
- package/runtime/scripts/hfs/slot-errors.mjs +8 -7
- package/runtime/scripts/hfs/slot-imports.mjs +70 -63
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +74 -52
- package/runtime/scripts/hfs/slot-match.mjs +122 -111
- package/runtime/scripts/hfs/slot-path.mjs +0 -2
- package/runtime/scripts/hfs/slot-required.mjs +65 -59
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +138 -112
- package/runtime/scripts/hfs/slot-side-problems.mjs +23 -19
- package/runtime/scripts/hfs/slots.mjs +175 -152
- package/runtime/scripts/hfs/typescript-programs.mjs +66 -69
- package/runtime/scripts/hfs/view.mjs +38 -40
- package/runtime/scripts/lib/glob.mjs +23 -14
- package/runtime/scripts/lib/grammar-lint.mjs +33 -37
- package/runtime/scripts/lib/i18n.mjs +32 -35
- package/runtime/scripts/lib/json-clone.mjs +45 -36
- package/runtime/scripts/lib/json.mjs +26 -14
- package/runtime/scripts/lib/list.mjs +9 -14
- package/runtime/scripts/lib/path-key.mjs +53 -38
- package/runtime/scripts/lib/read-text.mjs +30 -17
- package/runtime/scripts/lib/regex.mjs +1 -2
- package/runtime/scripts/lib/source-phrases.mjs +13 -13
- package/runtime/scripts/lib/ts-analysis.mjs +28 -26
- package/runtime/scripts/lib/ts-ast.mjs +36 -42
- package/runtime/scripts/lib/walk.mjs +33 -27
- package/runtime/scripts/lib/yaml-cached.mjs +4 -4
|
@@ -4,86 +4,92 @@ import path from 'node:path';
|
|
|
4
4
|
import { braceVariants } from '../lib/glob.mjs';
|
|
5
5
|
import { classifyIn, slotEnabled } from './slot-classify.mjs';
|
|
6
6
|
import { fillVars, varsOf } from './slot-match.mjs';
|
|
7
|
-
|
|
8
7
|
/** A populated fe.modules instance needs its index.ts only when it contains TypeScript, including nested files. */
|
|
9
8
|
export function requiredFileApplies(slotId, target, files) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
if (!/^fe\.modules(?:\.|$)/u.test(slotId) || path.posix.basename(target) !== 'index.ts')
|
|
10
|
+
return true;
|
|
11
|
+
const prefix = `${path.posix.dirname(target)}/`;
|
|
12
|
+
let populated = false;
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
if (!file.startsWith(prefix))
|
|
15
|
+
continue;
|
|
16
|
+
populated = true;
|
|
17
|
+
if (/\.tsx?$/u.test(file))
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return !populated;
|
|
19
21
|
}
|
|
20
|
-
|
|
21
22
|
/** One `requires` entry of a slot placed under `root`: a leading / makes it repository-relative, the variables are filled from `bindings`. */
|
|
22
23
|
function requiredEntry(entry, bindings, root) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const rooted = entry.startsWith('/');
|
|
25
|
+
const filled = fillVars(rooted ? entry.slice(1) : entry, bindings);
|
|
26
|
+
return rooted || !root || root === '.' ? filled : `${root}/${filled}`;
|
|
26
27
|
}
|
|
27
|
-
|
|
28
28
|
/** The files and directories the instance holding `p` must contain (directories end with /); {path} entries are repo-relative. */
|
|
29
29
|
export function requiredFilesIn(scope, input) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const c = classifyIn(scope, input);
|
|
31
|
+
if (!c.slot)
|
|
32
|
+
return [];
|
|
33
|
+
const slot = scope.byId.get(c.slot);
|
|
34
|
+
return (slot.requires ?? []).map((entry) => requiredEntry(entry, c.bindings, c.root));
|
|
34
35
|
}
|
|
35
|
-
|
|
36
36
|
function growBindings(combos, name, values) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
const next = [];
|
|
38
|
+
for (const combo of combos) {
|
|
39
|
+
for (const value of values)
|
|
40
|
+
next.push({ ...combo, [name]: value });
|
|
41
|
+
}
|
|
42
|
+
combos.splice(0, combos.length, ...next);
|
|
42
43
|
}
|
|
43
|
-
|
|
44
44
|
const expandApps = (repo, slot) => repo.apps.filter((a) => slot.appKind === undefined || a.kind === slot.appKind);
|
|
45
|
-
|
|
46
45
|
function appendRequiredVariantPaths(repo, slot, variant, paths) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
const names = [...new Set(varsOf(variant))];
|
|
47
|
+
const fixed = { ...slot.requiredInstances };
|
|
48
|
+
const open = names.filter((name) => name !== 'app' && !(name in fixed));
|
|
49
|
+
if (open.length)
|
|
50
|
+
return;
|
|
51
|
+
const combos = [{}];
|
|
52
|
+
if (names.includes('app'))
|
|
53
|
+
growBindings(combos, 'app', expandApps(repo, slot).map((app) => app.name));
|
|
54
|
+
for (const [name, values] of Object.entries(fixed))
|
|
55
|
+
growBindings(combos, name, values);
|
|
56
|
+
const isInstance = names.length > 0;
|
|
57
|
+
for (const bindings of combos) {
|
|
58
|
+
const target = fillVars(variant, bindings);
|
|
59
|
+
paths.push({ slot: slot.id, path: target, via: isInstance ? 'instance' : 'slot' });
|
|
60
|
+
const root = target.endsWith('/') ? target.slice(0, -1) : path.posix.dirname(target);
|
|
61
|
+
for (const entry of slot.requires ?? [])
|
|
62
|
+
paths.push({ slot: slot.id, path: requiredEntry(entry, bindings, root), via: 'requires' });
|
|
63
|
+
}
|
|
61
64
|
}
|
|
62
|
-
|
|
63
65
|
function appendRequiredSlotPaths(scope, slot, paths, minimums) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
if (slot.presence !== 'required' || !slotEnabled(scope, slot))
|
|
67
|
+
return;
|
|
68
|
+
if (slot.requiredWhen === 'connections' && !scope.repo.connections.length)
|
|
69
|
+
return;
|
|
70
|
+
if (slot.minInstances)
|
|
71
|
+
minimums.push({ slot: slot.id, min: slot.minInstances, ...(slot.appKind ? { appKind: slot.appKind } : {}) });
|
|
72
|
+
for (const variant of braceVariants(slot.path))
|
|
73
|
+
appendRequiredVariantPaths(scope.repo, slot, variant, paths);
|
|
68
74
|
}
|
|
69
|
-
|
|
70
75
|
function uniqueRequiredPaths(paths) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
return paths.filter((entry) => {
|
|
78
|
+
const key = `${entry.slot}|${entry.path}`;
|
|
79
|
+
if (seen.has(key))
|
|
80
|
+
return false;
|
|
81
|
+
seen.add(key);
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
78
84
|
}
|
|
79
|
-
|
|
80
85
|
/**
|
|
81
86
|
* What the repository must contain, without walking it: {paths: [{slot, path, via}], minimums: [{slot, min}]}.
|
|
82
87
|
* via is slot (a fixed path), instance (a requiredInstances or app-kind root) or requires (a file an instance needs).
|
|
83
88
|
*/
|
|
84
89
|
export function requiredPathsIn(scope) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
const paths = [];
|
|
91
|
+
const minimums = [];
|
|
92
|
+
for (const slot of scope.slots)
|
|
93
|
+
appendRequiredSlotPaths(scope, slot, paths, minimums);
|
|
94
|
+
return { paths: uniqueRequiredPaths(paths), minimums };
|
|
89
95
|
}
|
|
@@ -3,147 +3,173 @@ import { isPlainObject } from '../../engine/plain-object.mjs';
|
|
|
3
3
|
import { triggerProblems } from './declaration-slots.mjs';
|
|
4
4
|
import { litePresenceOf, slotInEdition } from './edition-slots.mjs';
|
|
5
5
|
import { manifestKind, RUNTIME_KIND, runtimeSemanticProblems, tierKindProblems } from './manifest-shape.mjs';
|
|
6
|
-
import { APP_SCOPE, PROFILES } from './slot-manifest-shape.mjs';
|
|
7
|
-
|
|
6
|
+
import { APP_SCOPE, isSide, PROFILES } from './slot-manifest-shape.mjs';
|
|
8
7
|
function checkAppRootSlot(slot, bad) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
for (const key of ['appKind', 'owner', 'layers', 'kinds', 'composedBy', 'requiredWhen', 'requiredInstances']) {
|
|
9
|
+
if (slot[key] !== undefined)
|
|
10
|
+
bad.push(`slot ${slot.id}: an app-root slot has no ${key}`);
|
|
11
|
+
}
|
|
12
|
+
if (slot.tier !== 'none')
|
|
13
|
+
bad.push(`slot ${slot.id}: an app-root slot has tier none`);
|
|
13
14
|
}
|
|
14
|
-
|
|
15
15
|
function checkSideSlot(slot, profile, manifest, bad) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const tiers = manifest.tiers[profile];
|
|
17
|
+
if (slot.tier !== 'none' && slot.tier !== 'inherit' && !(slot.tier in tiers))
|
|
18
|
+
bad.push(`slot ${slot.id}: tier ${slot.tier} is not a ${profile} tier`);
|
|
19
|
+
if (slot.appKind !== undefined && !manifest.appKinds[profile].includes(slot.appKind))
|
|
20
|
+
bad.push(`slot ${slot.id}: app kind ${slot.appKind} is not a ${profile} kind`);
|
|
21
|
+
bad.push(...triggerProblems(slot, manifest.triggerKinds));
|
|
20
22
|
}
|
|
21
|
-
|
|
22
23
|
function claimSlotPatterns(slot, profile, claimed, bad, braceVariants) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
if (slot.appKind !== undefined)
|
|
25
|
+
return;
|
|
26
|
+
for (const variant of braceVariants(slot.path)) {
|
|
27
|
+
const key = `${profile}:${variant}`;
|
|
28
|
+
if (claimed.has(key))
|
|
29
|
+
bad.push(`slots ${claimed.get(key)} and ${slot.id} claim the same pattern ${variant} on ${profile}`);
|
|
30
|
+
else
|
|
31
|
+
claimed.set(key, slot.id);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
31
34
|
function checkSlotProfiles(slot, manifest, claimed, bad, braceVariants) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
for (const profile of slot.profiles) {
|
|
36
|
+
if (profile === APP_SCOPE)
|
|
37
|
+
checkAppRootSlot(slot, bad);
|
|
38
|
+
else
|
|
39
|
+
checkSideSlot(slot, profile, manifest, bad);
|
|
40
|
+
claimSlotPatterns(slot, profile, claimed, bad, braceVariants);
|
|
41
|
+
}
|
|
37
42
|
}
|
|
38
|
-
|
|
39
43
|
function checkLitePath(slot, pathVars, varsOf, bad, braceVariants, compileVariant) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
if (slot.lite?.path === undefined)
|
|
45
|
+
return;
|
|
46
|
+
for (const name of varsOf(slot.lite.path))
|
|
47
|
+
if (!pathVars.has(name))
|
|
48
|
+
bad.push(`slot ${slot.id}: lite.path binds <${name}>, which the path does not`);
|
|
49
|
+
for (const variant of braceVariants(slot.lite.path)) {
|
|
50
|
+
try {
|
|
51
|
+
compileVariant(slot, variant);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
bad.push(`slot ${slot.id}: lite.path ${variant} does not compile (${error.message})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
|
-
|
|
47
58
|
function checkNamedPathBindings(slot, pathVars, varsOf, bad) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for (const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
for (const name of Object.keys(slot.requiredInstances ?? {})) {
|
|
60
|
+
if (!pathVars.has(name))
|
|
61
|
+
bad.push(`slot ${slot.id}: requiredInstances names <${name}>, which the path does not bind`);
|
|
62
|
+
}
|
|
63
|
+
for (const file of Object.values(slot.roles ?? {})) {
|
|
64
|
+
for (const name of varsOf(file))
|
|
65
|
+
if (!pathVars.has(name))
|
|
66
|
+
bad.push(`slot ${slot.id}: roles names <${name}> in ${file}, which the path does not bind`);
|
|
67
|
+
}
|
|
68
|
+
for (const entry of slot.requires ?? []) {
|
|
69
|
+
for (const name of varsOf(entry))
|
|
70
|
+
if (!pathVars.has(name))
|
|
71
|
+
bad.push(`slot ${slot.id}: requires ${entry} uses <${name}>, which the path does not bind`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
59
74
|
function checkSlotPatternCompilation(slot, bad, braceVariants, compileVariant) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
for (const variant of braceVariants(slot.path)) {
|
|
76
|
+
try {
|
|
77
|
+
compileVariant(slot, variant);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
bad.push(`slot ${slot.id}: pattern ${variant} does not compile (${error.message})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
63
83
|
}
|
|
64
|
-
|
|
65
84
|
function checkSlotComposition(slot, manifest, bad) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
for (const kind of slot.composedBy ?? []) {
|
|
86
|
+
if (!slot.profiles.every((profile) => manifest.appKinds[profile].includes(kind)))
|
|
87
|
+
bad.push(`slot ${slot.id}: composedBy names ${kind}, which is not an app kind of every profile of the slot`);
|
|
88
|
+
}
|
|
89
|
+
if (slot.layers !== undefined && slot.tier !== 'none' && !slot.profiles.every((profile) => manifest.tiers[profile][slot.tier]?.lowerLayerOnly))
|
|
90
|
+
bad.push(`slot ${slot.id}: layers need a lowerLayerOnly tier`);
|
|
70
91
|
}
|
|
71
|
-
|
|
72
92
|
function checkLiteDeclaration(slot, manifest, bad) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
if (!slotInEdition(manifest, slot, 'lite') && (slot.litePresence !== undefined || slot.lite !== undefined))
|
|
94
|
+
bad.push(`slot ${slot.id}: litePresence/lite on a slot lite never sees (editions)`);
|
|
95
|
+
if (isPlainObject(slot.litePresence) && slot.profiles.includes(APP_SCOPE))
|
|
96
|
+
bad.push(`slot ${slot.id}: an app-root slot takes a bare litePresence, not a per-side map`);
|
|
97
|
+
for (const profile of slot.profiles) {
|
|
98
|
+
if (litePresenceOf(slot, profile) === 'forbidden' && typeof slot.goesTo !== 'string')
|
|
99
|
+
bad.push(`slot ${slot.id}: forbidden in lite for ${profile}, so it says where the content goes (goesTo)`);
|
|
100
|
+
}
|
|
78
101
|
}
|
|
79
|
-
|
|
80
102
|
function checkSlotSemantics(slot, manifest, claimed, bad, helpers) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
if (claimed.ids.has(slot.id))
|
|
104
|
+
bad.push(`slot id ${slot.id} appears twice`);
|
|
105
|
+
claimed.ids.add(slot.id);
|
|
106
|
+
const pathVars = new Set(helpers.varsOf(slot.path));
|
|
107
|
+
checkSlotProfiles(slot, manifest, claimed.patterns, bad, helpers.braceVariants);
|
|
108
|
+
if (slot.appKind !== undefined && !pathVars.has('app'))
|
|
109
|
+
bad.push(`slot ${slot.id}: an app-kind slot binds <app> in its path`);
|
|
110
|
+
if (slot.requiredWhen !== undefined && slot.presence !== 'required')
|
|
111
|
+
bad.push(`slot ${slot.id}: requiredWhen belongs to a required slot`);
|
|
112
|
+
checkLiteDeclaration(slot, manifest, bad);
|
|
113
|
+
checkLitePath(slot, pathVars, helpers.varsOf, bad, helpers.braceVariants, helpers.compileVariant);
|
|
114
|
+
checkNamedPathBindings(slot, pathVars, helpers.varsOf, bad);
|
|
115
|
+
checkSlotPatternCompilation(slot, bad, helpers.braceVariants, helpers.compileVariant);
|
|
116
|
+
checkSlotComposition(slot, manifest, bad);
|
|
117
|
+
}
|
|
94
118
|
/** The problem of one declared side read, or null when a slot of the side it names (or of the app root) owns the path. */
|
|
95
119
|
function sideReadProblem(manifest, side, read, braceVariants) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
120
|
+
const [owner, ...rest] = read.split('/');
|
|
121
|
+
if (owner === side)
|
|
122
|
+
return `sides.${side}.reads names ${read}, which is its own side`;
|
|
123
|
+
const ownedBySide = isSide(owner);
|
|
124
|
+
const profile = ownedBySide ? owner : APP_SCOPE;
|
|
125
|
+
const prefix = ownedBySide ? rest.join('/') : read;
|
|
126
|
+
const owned = manifest.slots.some((slot) => slot.profiles.includes(profile) && slot.presence !== 'forbidden' && braceVariants(slot.path).some((variant) => variant.startsWith(prefix)));
|
|
127
|
+
const ownerName = ownedBySide ? owner : 'app-root';
|
|
128
|
+
return owned ? null : `sides.${side}.reads names ${read}, which no ${ownerName} slot owns`;
|
|
129
|
+
}
|
|
106
130
|
function checkSideReads(manifest, bad, braceVariants) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
for (const side of PROFILES) {
|
|
132
|
+
for (const read of manifest.sides[side].reads) {
|
|
133
|
+
const problem = sideReadProblem(manifest, side, read, braceVariants);
|
|
134
|
+
if (problem)
|
|
135
|
+
bad.push(problem);
|
|
136
|
+
}
|
|
111
137
|
}
|
|
112
|
-
}
|
|
113
138
|
}
|
|
114
|
-
|
|
115
139
|
function checkProfileTierTargets(manifest, profile, bad) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
bad.push(...tierKindProblems(manifest, profile));
|
|
141
|
+
for (const [tier, def] of Object.entries(manifest.tiers[profile])) {
|
|
142
|
+
for (const target of def.mayImport) {
|
|
143
|
+
if (!(target in manifest.tiers[profile]))
|
|
144
|
+
bad.push(`tiers.${profile}.${tier}.mayImport names ${target}, which is not a ${profile} tier`);
|
|
145
|
+
}
|
|
120
146
|
}
|
|
121
|
-
}
|
|
122
147
|
}
|
|
123
|
-
|
|
124
148
|
function checkProfileAppKinds(manifest, profile, bad) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
for (const kind of manifest.appKinds[profile]) {
|
|
150
|
+
const owners = manifest.slots.filter((slot) => slot.profiles.includes(profile) && slot.appKind === kind);
|
|
151
|
+
if (owners.length !== 1)
|
|
152
|
+
bad.push(`${profile} app kind ${kind} must have exactly one slot (found ${owners.length})`);
|
|
153
|
+
}
|
|
129
154
|
}
|
|
130
|
-
|
|
131
155
|
function checkProfileMaps(manifest, bad) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
156
|
+
for (const profile of PROFILES) {
|
|
157
|
+
checkProfileTierTargets(manifest, profile, bad);
|
|
158
|
+
checkProfileAppKinds(manifest, profile, bad);
|
|
159
|
+
}
|
|
136
160
|
}
|
|
137
|
-
|
|
138
161
|
/** The rules a JSON Schema cannot state: unique ids, tiers named and reachable, app kinds, variables, no duplicate pattern. */
|
|
139
162
|
export function manifestSemanticProblems(manifest, helpers) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
const bad = [];
|
|
164
|
+
const claimed = { ids: new Set(), patterns: new Map() };
|
|
165
|
+
for (const slot of manifest.slots)
|
|
166
|
+
checkSlotSemantics(slot, manifest, claimed, bad, helpers);
|
|
167
|
+
if (manifestKind(manifest) === RUNTIME_KIND)
|
|
168
|
+
return [...bad, ...runtimeSemanticProblems(manifest)];
|
|
169
|
+
checkSideReads(manifest, bad, helpers.braceVariants);
|
|
170
|
+
checkProfileMaps(manifest, bad);
|
|
171
|
+
const major = Number(manifest.version.split('.')[0]);
|
|
172
|
+
if (String(manifest.schema) !== `starci/hfs-slots@${major}`)
|
|
173
|
+
bad.push(`schema ${manifest.schema} does not carry the major of version ${manifest.version}`);
|
|
174
|
+
return bad;
|
|
149
175
|
}
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
// slot-side-problems.mjs - declaration checks for one app side against the HFS manifest.
|
|
2
2
|
import { backingProblems, optionalSlotProblems } from './declaration-slots.mjs';
|
|
3
|
-
|
|
4
3
|
function sideAppKindProblems(manifest, side, apps) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
const bad = [];
|
|
5
|
+
for (const app of apps)
|
|
6
|
+
if (!manifest.appKinds[side].includes(app.kind))
|
|
7
|
+
bad.push(`${side} app ${app.name} has kind ${app.kind}, which is not a ${side} kind (${manifest.appKinds[side].join(', ')})`);
|
|
8
|
+
return bad;
|
|
8
9
|
}
|
|
9
|
-
|
|
10
10
|
function requiredAppKindProblems(manifest, side, declaration) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const bad = [];
|
|
12
|
+
const connections = declaration.connections ?? [];
|
|
13
|
+
for (const slot of manifest.slots) {
|
|
14
|
+
if (slot.appKind === undefined || !slot.profiles.includes(side) || slot.presence !== 'required')
|
|
15
|
+
continue;
|
|
16
|
+
if (slot.requiredWhen === 'connections' && !connections.length)
|
|
17
|
+
continue;
|
|
18
|
+
if (!declaration.apps.some((app) => app.kind === slot.appKind))
|
|
19
|
+
bad.push(`no ${side} app of kind ${slot.appKind} is declared (${slot.id} is required${slot.requiredWhen ? ' once a connection is declared' : ''})`);
|
|
20
|
+
}
|
|
21
|
+
return bad;
|
|
19
22
|
}
|
|
20
|
-
|
|
21
23
|
/** Problems for one side declaration, preserving the manifest's checks and their order. */
|
|
22
24
|
export function sideProblems(manifest, side, declaration, edition = 'full') {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const bad = sideAppKindProblems(manifest, side, declaration.apps);
|
|
26
|
+
bad.push(...optionalSlotProblems(manifest, side, declaration), ...backingProblems(manifest, side, declaration, edition));
|
|
27
|
+
for (const read of declaration.reads ?? [])
|
|
28
|
+
if (!manifest.sides[side].reads.includes(read))
|
|
29
|
+
bad.push(`sides.${side}.reads names ${read}; ${side} may read only ${manifest.sides[side].reads.join(', ') || 'nothing of the other side'}`);
|
|
30
|
+
bad.push(...requiredAppKindProblems(manifest, side, declaration));
|
|
31
|
+
return bad;
|
|
28
32
|
}
|