@ucm-kit/adapter-typescript 0.1.0 → 0.1.5
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/LICENSE +21 -0
- package/README.md +77 -12
- package/package.json +4 -3
- package/src/cli.mjs +0 -0
- package/src/index.d.mts +2 -0
- package/src/parite.mjs +39 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 speckaina
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,20 +1,85 @@
|
|
|
1
1
|
# @ucm-kit/adapter-typescript
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- `ucm check` le découvre automatiquement lorsqu'il est installé dans le
|
|
8
|
-
repository et compare les props, les BOOLEAN lues et les compositions ;
|
|
9
|
-
- `ucm-typescript` génère les unions d'enums dans
|
|
10
|
-
`src/generated/contracts/` (ou dans le dossier donné à `--out`).
|
|
3
|
+
Optional adapter for TypeScript repositories that consume UCM contracts. It adds
|
|
4
|
+
two capabilities without making [`@ucm-kit/core`](https://www.npmjs.com/package/@ucm-kit/core)
|
|
5
|
+
depend on the TypeScript compiler, a 23 MB dependency no other consumer should
|
|
6
|
+
pay for.
|
|
11
7
|
|
|
12
8
|
```sh
|
|
13
|
-
npm install --save-dev @ucm-kit/adapter-typescript@0.1.
|
|
9
|
+
npm install --save-dev @ucm-kit/adapter-typescript@0.1.5 @ucm-kit/cli@0.1.12
|
|
14
10
|
npx ucm-typescript
|
|
15
11
|
npx --no-install ucm check
|
|
16
12
|
```
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
## Static parity
|
|
15
|
+
|
|
16
|
+
`ucm check` discovers this package when it is installed in the repository, and
|
|
17
|
+
compares each contract to the component that implements it. Six gaps are
|
|
18
|
+
reported:
|
|
19
|
+
|
|
20
|
+
| Gap | What it means |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Missing prop | The contract declares a prop the component's public API does not expose |
|
|
23
|
+
| Incompatible type | The prop exists, with a type the contract does not allow |
|
|
24
|
+
| Value the code does not offer | The contract publishes an enum value the declared union leaves out |
|
|
25
|
+
| Unused boolean | A `BOOLEAN` prop is declared but never read by the component |
|
|
26
|
+
| Unused enum | An enum prop is declared but never read by the component |
|
|
27
|
+
| Wrong composition cardinality | A declared dependency is rendered a different number of times than the contract says |
|
|
28
|
+
|
|
29
|
+
Parity requires a `tsconfig.json` at the root. Props are read with the
|
|
30
|
+
TypeScript type checker; compositions are counted by their occurrences in JSX.
|
|
31
|
+
|
|
32
|
+
Every one of these **warns without blocking**. They accuse the code, not the
|
|
33
|
+
contract, and fixing them is a developer's job, not a re-export.
|
|
34
|
+
|
|
35
|
+
## The convention composition counting assumes
|
|
36
|
+
|
|
37
|
+
**Counting is static: a dependency rendered by a loop is not counted.** Keep
|
|
38
|
+
every occurrence explicit in the source, and neutralise in place the one a
|
|
39
|
+
given view does not show, rather than removing it. Without that, a list built
|
|
40
|
+
with `.map()` reports a cardinality gap the contract did not intend.
|
|
41
|
+
|
|
42
|
+
This is a convention, not a mechanism: there is no exception list, no
|
|
43
|
+
annotation, and no per-component opt-out. The warning itself says what it does
|
|
44
|
+
not see, and it blocks nothing.
|
|
45
|
+
|
|
46
|
+
## What it does not measure
|
|
47
|
+
|
|
48
|
+
Enum values are compared by the **declared union only**. A union smaller than
|
|
49
|
+
the contract is reported; a union that accepts more is not, because accepting
|
|
50
|
+
more contradicts nothing. On a widened type such as `string`, no value is
|
|
51
|
+
resolved and nothing is reported.
|
|
52
|
+
|
|
53
|
+
What the component does with a value stays outside the static guarantee. A
|
|
54
|
+
`switch` with a `default`, a partial mapping table, a value forwarded to a
|
|
55
|
+
child, a table held in another file and a business rule that substitutes one
|
|
56
|
+
value for another are all legitimate; telling them apart from an oversight
|
|
57
|
+
would require the contract to describe behaviour. A contract describes the
|
|
58
|
+
views that exist; it says nothing about the logic that picks one.
|
|
59
|
+
|
|
60
|
+
A prop relayed through `{...rest}` without being read is reported as unused.
|
|
61
|
+
Following a spread would mean knowing the child's contract, and the warning
|
|
62
|
+
blocks nothing.
|
|
63
|
+
|
|
64
|
+
Nothing here executes a render. Parity is a comparison of surfaces.
|
|
65
|
+
|
|
66
|
+
None of this exists for a repository without this adapter, so the **absence of
|
|
67
|
+
these messages means nothing**: it is a capability of one adapter, never a
|
|
68
|
+
guarantee of the format.
|
|
69
|
+
|
|
70
|
+
## Generated types
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npx ucm-typescript # writes to src/generated/contracts/
|
|
74
|
+
npx ucm-typescript --out <dir>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It derives the enum unions from the contracts, so that a component's props can
|
|
78
|
+
be typed against the contract rather than against a hand-copied list.
|
|
79
|
+
|
|
80
|
+
## Status
|
|
81
|
+
|
|
82
|
+
**0.x, the public surface is not frozen.** Pin an exact version, without `^`.
|
|
83
|
+
|
|
84
|
+
- [Repository and issues](https://github.com/Vassili-g/UCM-Exporter)
|
|
85
|
+
- [MIT licensed](https://github.com/Vassili-g/UCM-Exporter/blob/main/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucm-kit/adapter-typescript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Adaptateur TypeScript UCM : parité statique des composants et génération des types dérivés des contrats.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src",
|
|
23
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
24
25
|
],
|
|
25
26
|
"exports": {
|
|
26
27
|
".": {
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
"test": "node scripts/run-tests.cjs"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@ucm-kit/core": "0.1.
|
|
38
|
+
"@ucm-kit/core": "0.1.16",
|
|
38
39
|
"typescript": ">=5.5.0 <6"
|
|
39
40
|
}
|
|
40
41
|
}
|
package/src/cli.mjs
CHANGED
|
File without changes
|
package/src/index.d.mts
CHANGED
|
@@ -18,7 +18,9 @@ export function ecartsDeParite(
|
|
|
18
18
|
fonctionAbsente: string | null;
|
|
19
19
|
manquantes: string[];
|
|
20
20
|
typesIncorrects: Array<{ prop: string; attendu: string; recu: string }>;
|
|
21
|
+
valeursNonImplementees: Array<{ prop: string; valeurs: string[] }>;
|
|
21
22
|
booleensNonUtilises: string[];
|
|
23
|
+
enumsSansEffet: Array<{ prop: string; valeurs: string[] }>;
|
|
22
24
|
compositionsIncorrectes: Array<{ component: string; attendu: number; rendu: number }>;
|
|
23
25
|
};
|
|
24
26
|
export function typesDuContrat(contrat: unknown): null | {
|
package/src/parite.mjs
CHANGED
|
@@ -285,9 +285,18 @@ export function lireApiPublique(fichiers, racine) {
|
|
|
285
285
|
const estBoolean = significatifs.length > 0 && significatifs.every(
|
|
286
286
|
(item) => Boolean(item.flags & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)),
|
|
287
287
|
);
|
|
288
|
+
// Le texte écrit dans le source ne dit rien d'un alias : `Ton | undefined`
|
|
289
|
+
// masque autant une union complète qu'une union amputée. La résolution,
|
|
290
|
+
// elle, rend les littéraux même dérivés d'un `as const`, et ne rend rien
|
|
291
|
+
// d'un type élargi, donc silence plutôt que faux positif.
|
|
292
|
+
const valeurs = significatifs.length > 0
|
|
293
|
+
&& significatifs.every((item) => item.isStringLiteral())
|
|
294
|
+
? significatifs.map((item) => item.value)
|
|
295
|
+
: null;
|
|
288
296
|
return [membre.getName(), {
|
|
289
297
|
type: estBoolean ? "boolean" : "autre",
|
|
290
298
|
typescript: verificateur.typeToString(typeMembre),
|
|
299
|
+
valeurs,
|
|
291
300
|
utilisee: consommees.has(membre.getName()),
|
|
292
301
|
}];
|
|
293
302
|
}));
|
|
@@ -312,7 +321,9 @@ export function ecartsDeParite(contrat, releve, nomInterface, options = {}) {
|
|
|
312
321
|
fonctionAbsente: null,
|
|
313
322
|
manquantes: [],
|
|
314
323
|
typesIncorrects: [],
|
|
324
|
+
valeursNonImplementees: [],
|
|
315
325
|
booleensNonUtilises: [],
|
|
326
|
+
enumsSansEffet: [],
|
|
316
327
|
compositionsIncorrectes: [],
|
|
317
328
|
};
|
|
318
329
|
if (!releve) {
|
|
@@ -360,8 +371,35 @@ export function ecartsDeParite(contrat, releve, nomInterface, options = {}) {
|
|
|
360
371
|
))
|
|
361
372
|
.map(([nom]) => nom)
|
|
362
373
|
.sort();
|
|
374
|
+
const enums = declarees.filter(([nom, prop]) => (
|
|
375
|
+
nom in props && prop?.type === "enum" && Array.isArray(prop.values)
|
|
376
|
+
));
|
|
377
|
+
// Une union plus large que le contrat ne se rapporte pas : accepter plus ne
|
|
378
|
+
// contredit rien, et le dire ferait du contrat un plafond.
|
|
379
|
+
const valeursNonImplementees = enums
|
|
380
|
+
.filter(([nom]) => Array.isArray(props[nom].valeurs))
|
|
381
|
+
.map(([nom, prop]) => ({
|
|
382
|
+
prop: nom,
|
|
383
|
+
valeurs: prop.values.filter((valeur) => !props[nom].valeurs.includes(valeur)),
|
|
384
|
+
}))
|
|
385
|
+
.filter(({ valeurs }) => valeurs.length > 0)
|
|
386
|
+
.sort((gauche, droite) => gauche.prop.localeCompare(droite.prop));
|
|
387
|
+
// Le même signal que `booleensNonUtilises`, sur le même relevé : le taire ici
|
|
388
|
+
// quand on le dit là serait une incohérence, pas une décision.
|
|
389
|
+
const enumsSansEffet = enums
|
|
390
|
+
.filter(([nom]) => props[nom].utilisee !== true)
|
|
391
|
+
.map(([nom, prop]) => ({ prop: nom, valeurs: [...prop.values] }))
|
|
392
|
+
.sort((gauche, droite) => gauche.prop.localeCompare(droite.prop));
|
|
363
393
|
|
|
364
|
-
return {
|
|
394
|
+
return {
|
|
395
|
+
...vide,
|
|
396
|
+
manquantes,
|
|
397
|
+
typesIncorrects,
|
|
398
|
+
valeursNonImplementees,
|
|
399
|
+
booleensNonUtilises,
|
|
400
|
+
enumsSansEffet,
|
|
401
|
+
compositionsIncorrectes,
|
|
402
|
+
};
|
|
365
403
|
}
|
|
366
404
|
|
|
367
405
|
/** Surface que `@ucm-kit/core/lecteurs` attend d'un adaptateur. */
|