@ontrails/warden 1.0.0-beta.19 → 1.0.0-beta.21
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 +37 -0
- package/package.json +9 -9
- package/src/index.ts +3 -0
- package/src/rules/index.ts +8 -0
- package/src/rules/metadata.ts +41 -0
- package/src/rules/no-retired-cross-vocabulary.ts +194 -0
- package/src/rules/public-export-example-coverage.ts +553 -0
- package/src/rules/registry-names.ts +6 -0
- package/src/rules/surface-facet-coherence.ts +370 -0
- package/src/trails/index.ts +3 -0
- package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
- package/src/trails/public-export-example-coverage.trail.ts +16 -0
- package/src/trails/surface-facet-coherence.trail.ts +25 -0
- package/src/trails/version-gap.trail.ts +1 -1
- package/src/trails/version-without-examples.trail.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @ontrails/warden
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.21
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5be032c: Add the repo-local `public-export-example-coverage` Warden rule (and its `publicExportExampleCoverageTrail` wrapper), graduating `scripts/check-public-api-examples.ts` into governed rule coverage. The rule anchors to this repository's five public surface package barrels via the rule module's own on-disk location, so it stays silent in consumer repositories.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [99523f2]
|
|
12
|
+
- Updated dependencies [3caa263]
|
|
13
|
+
- @ontrails/core@1.0.0-beta.21
|
|
14
|
+
- @ontrails/permits@1.0.0-beta.21
|
|
15
|
+
- @ontrails/topographer@1.0.0-beta.21
|
|
16
|
+
- @ontrails/adapter-kit@1.0.0-beta.21
|
|
17
|
+
- @ontrails/cli@1.0.0-beta.21
|
|
18
|
+
- @ontrails/store@1.0.0-beta.21
|
|
19
|
+
|
|
20
|
+
## 1.0.0-beta.20
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- 8bc0708: Add surface facet coherence diagnostics for selector overlap, visibility widening acknowledgements, dynamic selectors, and description hygiene.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 851a2a3: Derive trail caller and blaze input types from the authored input schema while keeping one public input contract.
|
|
29
|
+
- 6901776: Add a Warden rule and safe-fix metadata for rewriting retired cross vocabulary to compose vocabulary.
|
|
30
|
+
- Updated dependencies [851a2a3]
|
|
31
|
+
- Updated dependencies [eee1307]
|
|
32
|
+
- Updated dependencies [b248d4a]
|
|
33
|
+
- @ontrails/core@1.0.0-beta.20
|
|
34
|
+
- @ontrails/store@1.0.0-beta.20
|
|
35
|
+
- @ontrails/topographer@1.0.0-beta.20
|
|
36
|
+
- @ontrails/adapter-kit@1.0.0-beta.20
|
|
37
|
+
- @ontrails/cli@1.0.0-beta.20
|
|
38
|
+
- @ontrails/permits@1.0.0-beta.20
|
|
39
|
+
|
|
3
40
|
## 1.0.0-beta.19
|
|
4
41
|
|
|
5
42
|
### Major Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/warden",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.21",
|
|
4
4
|
"bin": {
|
|
5
5
|
"warden": "./bin/warden.ts"
|
|
6
6
|
},
|
|
@@ -28,20 +28,20 @@
|
|
|
28
28
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ontrails/adapter-kit": "^1.0.0-beta.
|
|
32
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
33
|
-
"@ontrails/permits": "^1.0.0-beta.
|
|
34
|
-
"@ontrails/store": "^1.0.0-beta.
|
|
31
|
+
"@ontrails/adapter-kit": "^1.0.0-beta.21",
|
|
32
|
+
"@ontrails/cli": "^1.0.0-beta.21",
|
|
33
|
+
"@ontrails/permits": "^1.0.0-beta.21",
|
|
34
|
+
"@ontrails/store": "^1.0.0-beta.21",
|
|
35
35
|
"oxc-parser": "^0.121.0",
|
|
36
36
|
"oxc-resolver": "11.19.1",
|
|
37
37
|
"zod": "^4.3.5"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@ontrails/config": "^1.0.0-beta.
|
|
41
|
-
"@ontrails/testing": "^1.0.0-beta.
|
|
40
|
+
"@ontrails/config": "^1.0.0-beta.21",
|
|
41
|
+
"@ontrails/testing": "^1.0.0-beta.21"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
45
|
-
"@ontrails/topographer": "^1.0.0-beta.
|
|
44
|
+
"@ontrails/core": "^1.0.0-beta.21",
|
|
45
|
+
"@ontrails/topographer": "^1.0.0-beta.21"
|
|
46
46
|
}
|
|
47
47
|
}
|
package/src/index.ts
CHANGED
|
@@ -188,6 +188,7 @@ export {
|
|
|
188
188
|
noLegacyLayerImportsTrail,
|
|
189
189
|
noNativeErrorResultTrail,
|
|
190
190
|
noRedundantResultErrorWrapTrail,
|
|
191
|
+
noRetiredCrossVocabularyTrail,
|
|
191
192
|
noSyncResultAssumptionTrail,
|
|
192
193
|
noThrowInDetourRecoverTrail,
|
|
193
194
|
noThrowInImplementationTrail,
|
|
@@ -199,6 +200,7 @@ export {
|
|
|
199
200
|
permitGovernanceTrail,
|
|
200
201
|
preferSchemaInferenceTrail,
|
|
201
202
|
projectAwareRuleInput,
|
|
203
|
+
publicExportExampleCoverageTrail,
|
|
202
204
|
publicInternalDeepImportsTrail,
|
|
203
205
|
publicOutputSchemaTrail,
|
|
204
206
|
publicUnionOutputDiscriminantsTrail,
|
|
@@ -214,6 +216,7 @@ export {
|
|
|
214
216
|
scheduledDestroyIntentTrail,
|
|
215
217
|
signalGraphCoachingTrail,
|
|
216
218
|
staticResourceAccessorPreferenceTrail,
|
|
219
|
+
surfaceFacetCoherenceTrail,
|
|
217
220
|
unmaterializedActivationSourceTrail,
|
|
218
221
|
topoAwareRuleInput,
|
|
219
222
|
unreachableDetourShadowingTrail,
|
package/src/rules/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { noLegacyLayerImports } from './no-legacy-layer-imports.js';
|
|
|
22
22
|
import { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
23
23
|
import { noNativeErrorResult } from './no-native-error-result.js';
|
|
24
24
|
import { noRedundantResultErrorWrap } from './no-redundant-result-error-wrap.js';
|
|
25
|
+
import { noRetiredCrossVocabulary } from './no-retired-cross-vocabulary.js';
|
|
25
26
|
import { noSyncResultAssumption } from './no-sync-result-assumption.js';
|
|
26
27
|
import { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
|
|
27
28
|
import { noThrowInImplementation } from './no-throw-in-implementation.js';
|
|
@@ -31,6 +32,7 @@ import { orphanedSignal } from './orphaned-signal.js';
|
|
|
31
32
|
import { ownerProjectionParity } from './owner-projection-parity.js';
|
|
32
33
|
import { permitGovernance } from './permit-governance.js';
|
|
33
34
|
import { preferSchemaInference } from './prefer-schema-inference.js';
|
|
35
|
+
import { publicExportExampleCoverage } from './public-export-example-coverage.js';
|
|
34
36
|
import { publicInternalDeepImports } from './public-internal-deep-imports.js';
|
|
35
37
|
import { publicOutputSchema } from './public-output-schema.js';
|
|
36
38
|
import { publicUnionOutputDiscriminants } from './public-union-output-discriminants.js';
|
|
@@ -44,6 +46,7 @@ import { resourceMockCoverage } from './resource-mock-coverage.js';
|
|
|
44
46
|
import { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
|
|
45
47
|
import { signalGraphCoaching } from './signal-graph-coaching.js';
|
|
46
48
|
import { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
|
|
49
|
+
import { surfaceFacetCoherence } from './surface-facet-coherence.js';
|
|
47
50
|
import {
|
|
48
51
|
forkWithoutPreservedBlaze,
|
|
49
52
|
markerSchemaUnsupported,
|
|
@@ -124,6 +127,7 @@ export { noLegacyLayerImports } from './no-legacy-layer-imports.js';
|
|
|
124
127
|
export { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
125
128
|
export { noNativeErrorResult } from './no-native-error-result.js';
|
|
126
129
|
export { noRedundantResultErrorWrap } from './no-redundant-result-error-wrap.js';
|
|
130
|
+
export { noRetiredCrossVocabulary } from './no-retired-cross-vocabulary.js';
|
|
127
131
|
export { noSyncResultAssumption } from './no-sync-result-assumption.js';
|
|
128
132
|
export { implementationReturnsResult } from './implementation-returns-result.js';
|
|
129
133
|
export { noThrowInDetourRecover } from './no-throw-in-detour-recover.js';
|
|
@@ -145,6 +149,7 @@ export { resourceMockCoverage } from './resource-mock-coverage.js';
|
|
|
145
149
|
export { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
|
|
146
150
|
export { signalGraphCoaching } from './signal-graph-coaching.js';
|
|
147
151
|
export { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
|
|
152
|
+
export { surfaceFacetCoherence } from './surface-facet-coherence.js';
|
|
148
153
|
export {
|
|
149
154
|
forkWithoutPreservedBlaze,
|
|
150
155
|
markerSchemaUnsupported,
|
|
@@ -186,6 +191,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
186
191
|
[onReferencesExist.name, onReferencesExist],
|
|
187
192
|
[orphanedSignal.name, orphanedSignal],
|
|
188
193
|
[ownerProjectionParity.name, ownerProjectionParity],
|
|
194
|
+
[publicExportExampleCoverage.name, publicExportExampleCoverage],
|
|
189
195
|
[publicInternalDeepImports.name, publicInternalDeepImports],
|
|
190
196
|
[resourceDeclarations.name, resourceDeclarations],
|
|
191
197
|
[readIntentFires.name, readIntentFires],
|
|
@@ -196,6 +202,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
196
202
|
[resourceMockCoverage.name, resourceMockCoverage],
|
|
197
203
|
[preferSchemaInference.name, preferSchemaInference],
|
|
198
204
|
[staticResourceAccessorPreference.name, staticResourceAccessorPreference],
|
|
205
|
+
[surfaceFacetCoherence.name, surfaceFacetCoherence],
|
|
199
206
|
[validDescribeRefs.name, validDescribeRefs],
|
|
200
207
|
[noDevPermitInSource.name, noDevPermitInSource],
|
|
201
208
|
[noDestructuredCompose.name, noDestructuredCompose],
|
|
@@ -203,6 +210,7 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
203
210
|
[noLegacyLayerImports.name, noLegacyLayerImports],
|
|
204
211
|
[noNativeErrorResult.name, noNativeErrorResult],
|
|
205
212
|
[noRedundantResultErrorWrap.name, noRedundantResultErrorWrap],
|
|
213
|
+
[noRetiredCrossVocabulary.name, noRetiredCrossVocabulary],
|
|
206
214
|
[noSyncResultAssumption.name, noSyncResultAssumption],
|
|
207
215
|
[implementationReturnsResult.name, implementationReturnsResult],
|
|
208
216
|
[noThrowInDetourRecover.name, noThrowInDetourRecover],
|
package/src/rules/metadata.ts
CHANGED
|
@@ -87,6 +87,7 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
|
87
87
|
'no-direct-implementation-call': 'composition',
|
|
88
88
|
'no-native-error-result': 'results',
|
|
89
89
|
'no-redundant-result-error-wrap': 'results',
|
|
90
|
+
'no-retired-cross-vocabulary': 'composition',
|
|
90
91
|
'no-sync-result-assumption': 'results',
|
|
91
92
|
'no-throw-in-detour-recover': 'results',
|
|
92
93
|
'no-throw-in-implementation': 'results',
|
|
@@ -104,6 +105,7 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
|
104
105
|
'scheduled-destroy-intent': 'lifecycle',
|
|
105
106
|
'signal-graph-coaching': 'signals',
|
|
106
107
|
'static-resource-accessor-preference': 'resources',
|
|
108
|
+
'surface-facet-coherence': 'meta',
|
|
107
109
|
'unmaterialized-activation-source': 'lifecycle',
|
|
108
110
|
'valid-detour-contract': 'results',
|
|
109
111
|
'version-gap': 'lifecycle',
|
|
@@ -295,6 +297,18 @@ const builtinWardenRuleMetadataInput = {
|
|
|
295
297
|
'Result error pass-throughs preserve the original Result boundary.',
|
|
296
298
|
tier: 'source-static',
|
|
297
299
|
},
|
|
300
|
+
'no-retired-cross-vocabulary': {
|
|
301
|
+
fix: { class: 'term-rewrite', safety: 'safe' },
|
|
302
|
+
invariant:
|
|
303
|
+
'Retired cross composition vocabulary does not remain in downstream source after the beta.19 compose cutover.',
|
|
304
|
+
lifecycle: {
|
|
305
|
+
retireWhen:
|
|
306
|
+
'Downstream beta.19 cross-to-compose migration window closes and supported apps have adopted compose vocabulary.',
|
|
307
|
+
state: 'temporary',
|
|
308
|
+
},
|
|
309
|
+
scope: 'external',
|
|
310
|
+
tier: 'source-static',
|
|
311
|
+
},
|
|
298
312
|
'no-sync-result-assumption': {
|
|
299
313
|
...durableExternal,
|
|
300
314
|
invariant:
|
|
@@ -391,6 +405,12 @@ const builtinWardenRuleMetadataInput = {
|
|
|
391
405
|
scope: 'advisory',
|
|
392
406
|
tier: 'source-static',
|
|
393
407
|
},
|
|
408
|
+
'public-export-example-coverage': {
|
|
409
|
+
...durableRepoLocal,
|
|
410
|
+
invariant:
|
|
411
|
+
'Public API barrel exports carry leading @example TSDoc coverage.',
|
|
412
|
+
tier: 'source-static',
|
|
413
|
+
},
|
|
394
414
|
'public-internal-deep-imports': {
|
|
395
415
|
invariant: 'Cross-package imports stay on package-owned public exports.',
|
|
396
416
|
lifecycle: { state: 'durable' },
|
|
@@ -513,6 +533,27 @@ const builtinWardenRuleMetadataInput = {
|
|
|
513
533
|
scope: 'advisory',
|
|
514
534
|
tier: 'source-static',
|
|
515
535
|
},
|
|
536
|
+
'surface-facet-coherence': {
|
|
537
|
+
...durableExternal,
|
|
538
|
+
guidance: {
|
|
539
|
+
docs: [
|
|
540
|
+
{
|
|
541
|
+
label: 'Surface Facets ADR',
|
|
542
|
+
path: 'docs/adr/drafts/20260603-surface-facets-shape-dense-topos.md',
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
steps: [
|
|
546
|
+
'Keep facet selectors as explicit string literals or literal arrays when possible.',
|
|
547
|
+
'Ensure each public trail belongs to one facet owner.',
|
|
548
|
+
'Record explicit visibility-widening acceptance and stable-description metadata when a facet intentionally widens visibility.',
|
|
549
|
+
],
|
|
550
|
+
summary:
|
|
551
|
+
'Keep surface facet maps reviewable before they reach MCP projection.',
|
|
552
|
+
},
|
|
553
|
+
invariant:
|
|
554
|
+
'Surface facet maps avoid selector overlap, hidden visibility widening, and drift-prone dynamic selectors.',
|
|
555
|
+
tier: 'source-static',
|
|
556
|
+
},
|
|
516
557
|
'unmaterialized-activation-source': {
|
|
517
558
|
...durableExternal,
|
|
518
559
|
invariant:
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flags retired `cross` composition vocabulary after the beta.19 compose cutover.
|
|
3
|
+
*
|
|
4
|
+
* Exact authored terms that have a mechanical successor carry safe
|
|
5
|
+
* `term-rewrite` edits. Larger identifiers and type prefixes remain
|
|
6
|
+
* review-required because the text-only rule cannot prove the intended symbol
|
|
7
|
+
* boundary.
|
|
8
|
+
*/
|
|
9
|
+
import { resolve, sep } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import type { WardenDiagnostic, WardenFix, WardenRule } from './types.js';
|
|
12
|
+
|
|
13
|
+
const RULE_NAME = 'no-retired-cross-vocabulary';
|
|
14
|
+
|
|
15
|
+
const SAFE_REWRITES = [
|
|
16
|
+
{ from: 'ctx.cross', to: 'ctx.compose' },
|
|
17
|
+
{ from: 'crossInput', to: 'composeInput' },
|
|
18
|
+
{ from: 'crosses', to: 'composes' },
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
const REVIEW_TERMS = ['Cross', 'cross'] as const;
|
|
22
|
+
|
|
23
|
+
const IDENTIFIER_CHAR = /[$0-9A-Z_a-z]/u;
|
|
24
|
+
|
|
25
|
+
const ALLOWED_PATH_SUFFIXES: readonly string[] = [
|
|
26
|
+
'/docs/migration/cross-to-compose.md',
|
|
27
|
+
'/docs/releases/beta15-to-beta19.md',
|
|
28
|
+
'/docs/adr/0049-composition-is-compose-not-cross.md',
|
|
29
|
+
'/packages/warden/src/rules/no-retired-cross-vocabulary.ts',
|
|
30
|
+
'/packages/warden/src/rules/metadata.ts',
|
|
31
|
+
'/packages/warden/src/trails/no-retired-cross-vocabulary.trail.ts',
|
|
32
|
+
'/scripts/vocab-cutover-rewrite.ts',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
interface SafeRewriteMatch {
|
|
36
|
+
readonly from: string;
|
|
37
|
+
readonly index: number;
|
|
38
|
+
readonly to: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ReviewMatch {
|
|
42
|
+
readonly index: number;
|
|
43
|
+
readonly term: (typeof REVIEW_TERMS)[number];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const normalizePath = (filePath: string): string =>
|
|
47
|
+
resolve(filePath).split(sep).join('/');
|
|
48
|
+
|
|
49
|
+
const isAllowedFile = (filePath: string): boolean => {
|
|
50
|
+
const normalized = normalizePath(filePath);
|
|
51
|
+
return ALLOWED_PATH_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const isIdentifierChar = (value: string): boolean =>
|
|
55
|
+
value !== '' && IDENTIFIER_CHAR.test(value);
|
|
56
|
+
|
|
57
|
+
const isReviewPrefix = (
|
|
58
|
+
sourceCode: string,
|
|
59
|
+
index: number,
|
|
60
|
+
term: string
|
|
61
|
+
): boolean => !(term === 'cross' && sourceCode.startsWith('crosses', index));
|
|
62
|
+
|
|
63
|
+
const isStandaloneSpan = (
|
|
64
|
+
sourceCode: string,
|
|
65
|
+
start: number,
|
|
66
|
+
end: number
|
|
67
|
+
): boolean => {
|
|
68
|
+
const before = start === 0 ? '' : (sourceCode[start - 1] ?? '');
|
|
69
|
+
const after = sourceCode[end] ?? '';
|
|
70
|
+
return !(isIdentifierChar(before) || isIdentifierChar(after));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const findSafeRewriteMatches = (
|
|
74
|
+
sourceCode: string
|
|
75
|
+
): readonly SafeRewriteMatch[] => {
|
|
76
|
+
const matches: SafeRewriteMatch[] = [];
|
|
77
|
+
for (const rewrite of SAFE_REWRITES) {
|
|
78
|
+
let fromIndex = 0;
|
|
79
|
+
while (fromIndex < sourceCode.length) {
|
|
80
|
+
const index = sourceCode.indexOf(rewrite.from, fromIndex);
|
|
81
|
+
if (index === -1) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
const end = index + rewrite.from.length;
|
|
85
|
+
if (isStandaloneSpan(sourceCode, index, end)) {
|
|
86
|
+
matches.push({ ...rewrite, index });
|
|
87
|
+
}
|
|
88
|
+
fromIndex = end;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return matches.toSorted((a, b) => a.index - b.index);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const findReviewMatches = (sourceCode: string): readonly ReviewMatch[] => {
|
|
95
|
+
const safeSpans = findSafeRewriteMatches(sourceCode).map((match) => ({
|
|
96
|
+
end: match.index + match.from.length,
|
|
97
|
+
start: match.index,
|
|
98
|
+
}));
|
|
99
|
+
const isInsideSafeSpan = (index: number): boolean =>
|
|
100
|
+
safeSpans.some((span) => index >= span.start && index < span.end);
|
|
101
|
+
|
|
102
|
+
const matches: ReviewMatch[] = [];
|
|
103
|
+
for (const term of REVIEW_TERMS) {
|
|
104
|
+
let fromIndex = 0;
|
|
105
|
+
while (fromIndex < sourceCode.length) {
|
|
106
|
+
const index = sourceCode.indexOf(term, fromIndex);
|
|
107
|
+
if (index === -1) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const end = index + term.length;
|
|
111
|
+
if (!isInsideSafeSpan(index)) {
|
|
112
|
+
const before = index === 0 ? '' : (sourceCode[index - 1] ?? '');
|
|
113
|
+
const after = sourceCode[end] ?? '';
|
|
114
|
+
if (
|
|
115
|
+
!isIdentifierChar(before) &&
|
|
116
|
+
isIdentifierChar(after) &&
|
|
117
|
+
isReviewPrefix(sourceCode, index, term)
|
|
118
|
+
) {
|
|
119
|
+
matches.push({ index, term });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
fromIndex = end;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return matches.toSorted((a, b) => a.index - b.index);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const lineForOffset = (sourceCode: string, offset: number): number => {
|
|
129
|
+
let line = 1;
|
|
130
|
+
for (let i = 0; i < offset; i += 1) {
|
|
131
|
+
if (sourceCode.codePointAt(i) === 10) {
|
|
132
|
+
line += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return line;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const safeFix = (match: SafeRewriteMatch): WardenFix => ({
|
|
139
|
+
class: 'term-rewrite',
|
|
140
|
+
edits: [
|
|
141
|
+
{
|
|
142
|
+
end: match.index + match.from.length,
|
|
143
|
+
replacement: match.to,
|
|
144
|
+
start: match.index,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
reason: `Retired composition vocabulary '${match.from}' has a mechanical beta.19 replacement '${match.to}'.`,
|
|
148
|
+
safety: 'safe',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const reviewFix = (term: string): WardenFix => ({
|
|
152
|
+
class: 'term-rewrite',
|
|
153
|
+
reason: `Retired composition vocabulary '${term}' appears in a larger or ambiguous form. Review before migrating to compose vocabulary.`,
|
|
154
|
+
safety: 'review',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const safeMessage = (match: SafeRewriteMatch): string =>
|
|
158
|
+
`Retired composition vocabulary '${match.from}' should be '${match.to}' after the beta.19 compose cutover.`;
|
|
159
|
+
|
|
160
|
+
const reviewMessage = (term: string): string =>
|
|
161
|
+
`Retired composition vocabulary '${term}' needs review before migrating to compose vocabulary.`;
|
|
162
|
+
|
|
163
|
+
export const noRetiredCrossVocabulary: WardenRule = {
|
|
164
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
165
|
+
if (isAllowedFile(filePath)) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const reviewMatches = findReviewMatches(sourceCode);
|
|
170
|
+
if (reviewMatches.length > 0) {
|
|
171
|
+
return reviewMatches.map((match) => ({
|
|
172
|
+
filePath,
|
|
173
|
+
fix: reviewFix(match.term),
|
|
174
|
+
line: lineForOffset(sourceCode, match.index),
|
|
175
|
+
message: reviewMessage(match.term),
|
|
176
|
+
rule: RULE_NAME,
|
|
177
|
+
severity: 'error',
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return findSafeRewriteMatches(sourceCode).map((match) => ({
|
|
182
|
+
filePath,
|
|
183
|
+
fix: safeFix(match),
|
|
184
|
+
line: lineForOffset(sourceCode, match.index),
|
|
185
|
+
message: safeMessage(match),
|
|
186
|
+
rule: RULE_NAME,
|
|
187
|
+
severity: 'error',
|
|
188
|
+
}));
|
|
189
|
+
},
|
|
190
|
+
description:
|
|
191
|
+
'Disallow retired cross composition vocabulary after the beta.19 compose cutover.',
|
|
192
|
+
name: RULE_NAME,
|
|
193
|
+
severity: 'error',
|
|
194
|
+
};
|