@fulcro/types 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -24
- package/dist/bigInteger/index.d.ts +2 -3
- package/dist/integer/index.d.ts +4 -6
- package/dist/struct/index.d.ts +47 -15
- package/dist/struct/index.js +73 -4
- package/package.json +1 -24
- package/dist/languageService/index.d.ts +0 -21
- package/dist/languageService/index.js +0 -23
- package/dist/transformer/classify/index.d.ts +0 -39
- package/dist/transformer/classify/index.js +0 -84
- package/dist/transformer/index.d.ts +0 -27
- package/dist/transformer/index.js +0 -30
- package/dist/transformer/rewriter/index.d.ts +0 -3
- package/dist/transformer/rewriter/index.js +0 -374
- package/dist/unplugin/index.d.mts +0 -15
- package/dist/unplugin/index.mjs +0 -31
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Numeric types with a declared range and layout: fixed-width integers, half,
|
|
4
4
|
single and double precision floats, an integer of any size, and a `Decimal` with
|
|
5
|
-
the semantics of IEEE 754 decimal128
|
|
6
|
-
|
|
5
|
+
the semantics of IEEE 754 decimal128. Nothing to configure: every operation is
|
|
6
|
+
a typed method.
|
|
7
7
|
|
|
8
8
|
```sh
|
|
9
9
|
npm install @fulcro/types
|
|
@@ -34,27 +34,9 @@ Each type has a value of the same name that converts, recognises and computes,
|
|
|
34
34
|
and every type but `BigInteger` reports its `minimum` and `maximum`. If you only
|
|
35
35
|
need the types, `import type` them and no code is loaded.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
integer, rounded on a float, decimal128 on a `Decimal` — and the result keeps
|
|
41
|
-
its type. Both operands have to be the same type, or it is a type error at the
|
|
42
|
-
line:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
const total = price * Decimal.from(3); // Decimal
|
|
46
|
-
count++; // SignedInteger<32>.increment, checked
|
|
47
|
-
a + 1; // type error: 'number' is not 'SignedInteger<32>'
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
| Tool | Wire in |
|
|
51
|
-
| ---------- | ---------------------------------------------------------------------------------------- |
|
|
52
|
-
| `tsc` | `{ "transform": "@fulcro/types/transformer", "transformProgram": true }`, via `ts-patch` |
|
|
53
|
-
| A bundler | `vite` (or `rollup`, `webpack`, `esbuild`, …) from `@fulcro/types/unplugin` |
|
|
54
|
-
| The editor | `{ "name": "@fulcro/types/language-service" }` in the tsconfig `plugins` |
|
|
55
|
-
|
|
56
|
-
Without it, the operators are the language's own: unchecked on the
|
|
57
|
-
`number`-backed types, refused on a `Decimal`.
|
|
37
|
+
The JavaScript operators are the language's own: `a + b` on two
|
|
38
|
+
`SignedInteger<32>` is a plain, unchecked `number`, and on a `Decimal` it
|
|
39
|
+
throws. Use the methods, which keep the type and its checks.
|
|
58
40
|
|
|
59
41
|
Every type but `BigInteger` declares a size and alignment, which
|
|
60
42
|
`sizeOf<T>()` and `alignOf<T>()` from `@fulcro/reflect` read at compile time.
|
|
@@ -82,5 +64,5 @@ Vector3.write(view, 0, Vector3.from({ x: 0, y: 1, z: 0 }));
|
|
|
82
64
|
---
|
|
83
65
|
|
|
84
66
|
**Full guide:** [docs/types.md](../../docs/types.md) — ranges, rounding, the
|
|
85
|
-
|
|
67
|
+
special values, the layout table and structs.
|
|
86
68
|
🇧🇷 [Leia em português](../../docs/pt-BR/types.md).
|
|
@@ -5,9 +5,8 @@ import type { NumericType } from '../numericType/index.js';
|
|
|
5
5
|
*
|
|
6
6
|
* Carried by a `bigint`, which is exact at every magnitude, and branded like
|
|
7
7
|
* every other numeric type here: a `BigInteger` is a `bigint` that went through
|
|
8
|
-
* {@link BigInteger.from}, not any `bigint` at all
|
|
9
|
-
*
|
|
10
|
-
* of a program.
|
|
8
|
+
* {@link BigInteger.from}, not any `bigint` at all, so an unchecked `bigint`
|
|
9
|
+
* cannot be passed where one is expected.
|
|
11
10
|
*
|
|
12
11
|
* It is also the one numeric type here with no fixed layout — its size is the
|
|
13
12
|
* size of its value — which is why `sizeOf<BigInteger>()` is a type error.
|
package/dist/integer/index.d.ts
CHANGED
|
@@ -39,9 +39,8 @@ export type IntegerRepresentation<N extends IntegerWidth> = N extends 64 | 128 ?
|
|
|
39
39
|
* Descriptor of a fixed-width integer type.
|
|
40
40
|
*
|
|
41
41
|
* Every operation is checked: a result outside the range throws a `RangeError`
|
|
42
|
-
* naming the operation, the value and the range
|
|
43
|
-
*
|
|
44
|
-
* modular behaviour of an `unchecked` conversion.
|
|
42
|
+
* naming the operation, the value and the range. `wrap` is the one exception,
|
|
43
|
+
* and the explicit way to ask for modular arithmetic.
|
|
45
44
|
*
|
|
46
45
|
* @template T Type of the values this descriptor produces.
|
|
47
46
|
*/
|
|
@@ -65,8 +64,7 @@ export interface IntegerType<T> extends BoundedNumericType<T, number | bigint> {
|
|
|
65
64
|
*/
|
|
66
65
|
wrap(value: number | bigint): T;
|
|
67
66
|
/**
|
|
68
|
-
* Divides, truncating the quotient towards zero as
|
|
69
|
-
* C#, Java and `BigInt`.
|
|
67
|
+
* Divides, truncating the quotient towards zero as `BigInt` division does.
|
|
70
68
|
*
|
|
71
69
|
* @param left Dividend.
|
|
72
70
|
* @param right Divisor.
|
|
@@ -130,7 +128,7 @@ export interface IntegerType<T> extends BoundedNumericType<T, number | bigint> {
|
|
|
130
128
|
/**
|
|
131
129
|
* Shifts the bits towards the least significant end with zeros coming in,
|
|
132
130
|
* as `>>>` does — on a signed type, over the bits of its own width, and read
|
|
133
|
-
* back as signed
|
|
131
|
+
* back as signed.
|
|
134
132
|
*
|
|
135
133
|
* ```ts
|
|
136
134
|
* const Int32 = SignedInteger(32);
|
package/dist/struct/index.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ interface FieldDescriptor {
|
|
|
12
12
|
type StructFields = {
|
|
13
13
|
readonly [field: string]: FieldDescriptor;
|
|
14
14
|
};
|
|
15
|
+
/** The methods of a struct, by name. */
|
|
16
|
+
type StructMethods = {
|
|
17
|
+
readonly [method: string]: (...parameters: never[]) => unknown;
|
|
18
|
+
};
|
|
19
|
+
/** What a struct declared without methods has: none. */
|
|
20
|
+
type NoMethods = Record<never, never>;
|
|
15
21
|
/** The type of the values a descriptor recognises. */
|
|
16
22
|
type ValueOf<TDescriptor> = TDescriptor extends {
|
|
17
23
|
is(value: unknown): value is infer T;
|
|
@@ -44,10 +50,16 @@ type StructAlignment<TFields extends StructFields> = LargestAlignment<{
|
|
|
44
50
|
* not promise an order for its keys.
|
|
45
51
|
*/
|
|
46
52
|
type StructSize<TFields extends StructFields> = RoundUp<SumOfSizes<TFields, UnionToTuple<keyof TFields>>, StructAlignment<TFields>>;
|
|
47
|
-
/**
|
|
48
|
-
type
|
|
53
|
+
/** The fields of a value of a struct, and its layout. */
|
|
54
|
+
type StructData<TFields extends StructFields> = {
|
|
49
55
|
readonly [TKey in keyof TFields]: ValueOf<TFields[TKey]>;
|
|
50
56
|
} & Layout<StructSize<TFields>, StructAlignment<TFields>>;
|
|
57
|
+
/**
|
|
58
|
+
* A value of a struct: its fields, and its methods when it declares any. A
|
|
59
|
+
* struct without methods is its fields alone, exactly as before methods
|
|
60
|
+
* existed, so nothing it inferred changes.
|
|
61
|
+
*/
|
|
62
|
+
type StructValue<TFields extends StructFields, TMethods extends StructMethods = NoMethods> = [keyof TMethods] extends [never] ? StructData<TFields> : StructData<TFields> & Readonly<TMethods>;
|
|
51
63
|
/** What a struct's `from` accepts: each field in what its own `from` accepts. */
|
|
52
64
|
type StructSource<TFields extends StructFields> = {
|
|
53
65
|
readonly [TKey in keyof TFields]: SourceOf<TFields[TKey]>;
|
|
@@ -57,8 +69,9 @@ type StructSource<TFields extends StructFields> = {
|
|
|
57
69
|
* recognised, compared and stored.
|
|
58
70
|
*
|
|
59
71
|
* @template TFields Descriptors of the fields, by name.
|
|
72
|
+
* @template TMethods Methods every value carries, by name; none by default.
|
|
60
73
|
*/
|
|
61
|
-
export interface StructType<TFields extends StructFields> {
|
|
74
|
+
export interface StructType<TFields extends StructFields, TMethods extends StructMethods = NoMethods> {
|
|
62
75
|
/** Name of the struct, as it reads in an error message. */
|
|
63
76
|
readonly name: string;
|
|
64
77
|
/**
|
|
@@ -85,40 +98,42 @@ export interface StructType<TFields extends StructFields> {
|
|
|
85
98
|
* Makes a value, converting each field with its own type's `from`.
|
|
86
99
|
*
|
|
87
100
|
* @param source One entry per field, and nothing else.
|
|
88
|
-
* @returns The value, frozen.
|
|
101
|
+
* @returns The value, frozen, carrying the struct's methods.
|
|
89
102
|
* @throws {TypeError} When a field is missing or not a field of the struct.
|
|
90
103
|
* @throws {RangeError} When a field's own conversion refuses its value; the
|
|
91
104
|
* message names the field, and `cause` is the original error.
|
|
92
105
|
*/
|
|
93
|
-
from(source: StructSource<TFields>): StructValue<TFields>;
|
|
106
|
+
from(source: StructSource<TFields>): StructValue<TFields, TMethods>;
|
|
94
107
|
/**
|
|
95
108
|
* Tells whether a value is one of this struct's: frozen, with exactly its
|
|
96
|
-
* fields, each of its type
|
|
109
|
+
* fields, each of its type — and, when the struct declares methods, made by
|
|
110
|
+
* this struct, since an object with the right fields alone would not carry
|
|
111
|
+
* them.
|
|
97
112
|
*
|
|
98
113
|
* @param value Value to inspect.
|
|
99
114
|
* @returns `true` when it is.
|
|
100
115
|
*/
|
|
101
|
-
is(value: unknown): value is StructValue<TFields>;
|
|
116
|
+
is(value: unknown): value is StructValue<TFields, TMethods>;
|
|
102
117
|
/**
|
|
103
118
|
* Compares two values field by field, each as its own type compares — so a
|
|
104
|
-
* `NaN` field makes a value unequal to itself, as
|
|
119
|
+
* `NaN` field makes a value unequal to itself, as `NaN` is.
|
|
105
120
|
*
|
|
106
121
|
* @param left First value.
|
|
107
122
|
* @param right Second value.
|
|
108
123
|
* @returns `true` when every field is equal.
|
|
109
124
|
*/
|
|
110
|
-
equals(left: StructValue<TFields>, right: StructValue<TFields>): boolean;
|
|
125
|
+
equals(left: StructValue<TFields, TMethods>, right: StructValue<TFields, TMethods>): boolean;
|
|
111
126
|
/**
|
|
112
127
|
* Reads a value from bytes, in the layout of {@link StructType.layout},
|
|
113
128
|
* little-endian.
|
|
114
129
|
*
|
|
115
130
|
* @param view Bytes to read from.
|
|
116
131
|
* @param offset Where the value starts.
|
|
117
|
-
* @returns The value, frozen.
|
|
132
|
+
* @returns The value, frozen, carrying the struct's methods.
|
|
118
133
|
* @throws {RangeError} When the struct does not fit in the view at that
|
|
119
134
|
* offset.
|
|
120
135
|
*/
|
|
121
|
-
read(view: DataView, offset: number): StructValue<TFields>;
|
|
136
|
+
read(view: DataView, offset: number): StructValue<TFields, TMethods>;
|
|
122
137
|
/**
|
|
123
138
|
* Writes a value into bytes, in the layout of {@link StructType.layout},
|
|
124
139
|
* little-endian. Padding bytes are left as they were.
|
|
@@ -129,7 +144,7 @@ export interface StructType<TFields extends StructFields> {
|
|
|
129
144
|
* @throws {RangeError} When the struct does not fit in the view at that
|
|
130
145
|
* offset.
|
|
131
146
|
*/
|
|
132
|
-
write(view: DataView, offset: number, value: StructValue<TFields>): void;
|
|
147
|
+
write(view: DataView, offset: number, value: StructValue<TFields, TMethods>): void;
|
|
133
148
|
}
|
|
134
149
|
/**
|
|
135
150
|
* The type of the values of a struct, named from its descriptor.
|
|
@@ -141,7 +156,7 @@ export interface StructType<TFields extends StructFields> {
|
|
|
141
156
|
*
|
|
142
157
|
* @template TDescriptor Type of the descriptor `struct` returned.
|
|
143
158
|
*/
|
|
144
|
-
export type Struct<TDescriptor extends StructType<StructFields>> = TDescriptor extends StructType<infer TFields> ? StructValue<TFields> : never;
|
|
159
|
+
export type Struct<TDescriptor extends StructType<StructFields, StructMethods>> = TDescriptor extends StructType<infer TFields, infer TMethods> ? StructValue<TFields, TMethods> : never;
|
|
145
160
|
/**
|
|
146
161
|
* Declares a struct: a value type with a fixed layout.
|
|
147
162
|
*
|
|
@@ -164,11 +179,28 @@ export type Struct<TDescriptor extends StructType<StructFields>> = TDescriptor e
|
|
|
164
179
|
* read back as the same value. It is still a JavaScript object while it is
|
|
165
180
|
* held as one; the layout is what it occupies when it is stored.
|
|
166
181
|
*
|
|
182
|
+
* Methods, when given, are shared by every value through one prototype: they
|
|
183
|
+
* take no bytes and are not fields, so the layout, `equals` and the bytes are
|
|
184
|
+
* the same as without them. `this` is the value, which is frozen — a method
|
|
185
|
+
* that changes something returns a new value:
|
|
186
|
+
*
|
|
187
|
+
* ```ts
|
|
188
|
+
* const Vector3 = struct('Vector3', { x: SinglePrecisionFloat, … }, {
|
|
189
|
+
* length() {
|
|
190
|
+
* return Math.hypot(this.x, this.y, this.z);
|
|
191
|
+
* },
|
|
192
|
+
* });
|
|
193
|
+
*
|
|
194
|
+
* Vector3.from({ x: 3, y: 4, z: 0 }).length(); // 5
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
167
197
|
* @param name Name of the struct, for error messages.
|
|
168
198
|
* @param fields Descriptor of each field, by name.
|
|
199
|
+
* @param methods Function of each method, by name.
|
|
169
200
|
* @returns The descriptor of the struct.
|
|
170
201
|
* @throws {TypeError} When there is no field, a field's type has no fixed
|
|
171
|
-
* layout,
|
|
202
|
+
* layout, a field is named like an array index or `~layout`, or a method is
|
|
203
|
+
* not a function or is named like a field, an array index or `~layout`.
|
|
172
204
|
*/
|
|
173
|
-
export declare const struct: <TFields extends StructFields>(name: string, fields: TFields) => StructType<TFields>;
|
|
205
|
+
export declare const struct: <TFields extends StructFields, TMethods extends StructMethods = NoMethods>(name: string, fields: TFields, methods?: TMethods & ThisType<StructValue<TFields, TMethods>>) => StructType<TFields, TMethods>;
|
|
174
206
|
export {};
|
package/dist/struct/index.js
CHANGED
|
@@ -36,6 +36,48 @@ const rethrowForField = (name, key, error) => {
|
|
|
36
36
|
}
|
|
37
37
|
throw error;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Builds the prototype every value of a struct with methods is made on.
|
|
41
|
+
*
|
|
42
|
+
* The methods sit on it, not on each value: a value is its fields and nothing
|
|
43
|
+
* else of its own, so a thousand values cost no function object each, and
|
|
44
|
+
* `Object.keys` still lists exactly the fields that `is` counts. The methods
|
|
45
|
+
* are not enumerable, so spreading a value copies its fields and not them.
|
|
46
|
+
*
|
|
47
|
+
* @param name Name of the struct.
|
|
48
|
+
* @param fields Its fields, which no method may be named like.
|
|
49
|
+
* @param methods The methods.
|
|
50
|
+
* @returns The prototype, frozen.
|
|
51
|
+
* @throws {TypeError} When `methods` is not an object, a method is not a
|
|
52
|
+
* function, or a method is named like a field, an array index or `~layout`.
|
|
53
|
+
*/
|
|
54
|
+
const methodPrototype = (name, fields, methods) => {
|
|
55
|
+
if (typeof methods !== 'object' || methods === null) {
|
|
56
|
+
throw new TypeError(`struct ${name}: expected an object of methods, received ${describeKind(methods)}.`);
|
|
57
|
+
}
|
|
58
|
+
const prototype = {};
|
|
59
|
+
for (const key of Reflect.ownKeys(methods)) {
|
|
60
|
+
const method = methods[key];
|
|
61
|
+
const label = String(key);
|
|
62
|
+
if (typeof key === 'string' && Object.hasOwn(fields, key)) {
|
|
63
|
+
throw new TypeError(`struct ${name}: method '${label}' has the name of a field; a value could not hold both.`);
|
|
64
|
+
}
|
|
65
|
+
if (typeof key === 'string' &&
|
|
66
|
+
(ARRAY_INDEX.test(key) || key === '~layout')) {
|
|
67
|
+
throw new TypeError(`struct ${name}: '${label}' cannot name a method; an array index would be reordered, and '~layout' is the layout itself.`);
|
|
68
|
+
}
|
|
69
|
+
if (typeof method !== 'function') {
|
|
70
|
+
throw new TypeError(`struct ${name}: method '${label}' must be a function, received ${describeKind(method)}.`);
|
|
71
|
+
}
|
|
72
|
+
Object.defineProperty(prototype, key, {
|
|
73
|
+
value: method,
|
|
74
|
+
enumerable: false,
|
|
75
|
+
writable: false,
|
|
76
|
+
configurable: false,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return Object.freeze(prototype);
|
|
80
|
+
};
|
|
39
81
|
/**
|
|
40
82
|
* Declares a struct: a value type with a fixed layout.
|
|
41
83
|
*
|
|
@@ -58,13 +100,30 @@ const rethrowForField = (name, key, error) => {
|
|
|
58
100
|
* read back as the same value. It is still a JavaScript object while it is
|
|
59
101
|
* held as one; the layout is what it occupies when it is stored.
|
|
60
102
|
*
|
|
103
|
+
* Methods, when given, are shared by every value through one prototype: they
|
|
104
|
+
* take no bytes and are not fields, so the layout, `equals` and the bytes are
|
|
105
|
+
* the same as without them. `this` is the value, which is frozen — a method
|
|
106
|
+
* that changes something returns a new value:
|
|
107
|
+
*
|
|
108
|
+
* ```ts
|
|
109
|
+
* const Vector3 = struct('Vector3', { x: SinglePrecisionFloat, … }, {
|
|
110
|
+
* length() {
|
|
111
|
+
* return Math.hypot(this.x, this.y, this.z);
|
|
112
|
+
* },
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* Vector3.from({ x: 3, y: 4, z: 0 }).length(); // 5
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
61
118
|
* @param name Name of the struct, for error messages.
|
|
62
119
|
* @param fields Descriptor of each field, by name.
|
|
120
|
+
* @param methods Function of each method, by name.
|
|
63
121
|
* @returns The descriptor of the struct.
|
|
64
122
|
* @throws {TypeError} When there is no field, a field's type has no fixed
|
|
65
|
-
* layout,
|
|
123
|
+
* layout, a field is named like an array index or `~layout`, or a method is
|
|
124
|
+
* not a function or is named like a field, an array index or `~layout`.
|
|
66
125
|
*/
|
|
67
|
-
const struct = (name, fields) => {
|
|
126
|
+
const struct = (name, fields, methods) => {
|
|
68
127
|
if (typeof name !== 'string' || name === '') {
|
|
69
128
|
throw new TypeError(`struct: expected a name, received ${describeKind(name)}.`);
|
|
70
129
|
}
|
|
@@ -85,6 +144,15 @@ const struct = (name, fields) => {
|
|
|
85
144
|
}
|
|
86
145
|
return { key, descriptor: fields[key], codec };
|
|
87
146
|
});
|
|
147
|
+
const prototype = methods === undefined ? undefined : methodPrototype(name, fields, methods);
|
|
148
|
+
/**
|
|
149
|
+
* Makes the object a value is built on: one carrying the methods, when the
|
|
150
|
+
* struct has any, and a plain one otherwise — so a struct without methods
|
|
151
|
+
* makes exactly the values it made before methods existed.
|
|
152
|
+
*
|
|
153
|
+
* @returns The object, still empty and not yet frozen.
|
|
154
|
+
*/
|
|
155
|
+
const blank = () => prototype === undefined ? {} : Object.create(prototype);
|
|
88
156
|
// Largest alignment first, so that every field lands aligned without padding
|
|
89
157
|
// before it: each size is a multiple of its own alignment. `sort` is stable,
|
|
90
158
|
// which keeps declaration order among equals.
|
|
@@ -132,7 +200,7 @@ const struct = (name, fields) => {
|
|
|
132
200
|
}
|
|
133
201
|
};
|
|
134
202
|
const readFields = (view, offset) => {
|
|
135
|
-
const value =
|
|
203
|
+
const value = blank();
|
|
136
204
|
for (const field of plan) {
|
|
137
205
|
value[field.key] = field.codec.read(view, offset + field.offset);
|
|
138
206
|
}
|
|
@@ -156,7 +224,7 @@ const struct = (name, fields) => {
|
|
|
156
224
|
throw new TypeError(`${name}.from: '${key}' is not a field; the fields are ${keys.join(', ')}.`);
|
|
157
225
|
}
|
|
158
226
|
}
|
|
159
|
-
const value =
|
|
227
|
+
const value = blank();
|
|
160
228
|
for (const field of plan) {
|
|
161
229
|
if (!Object.hasOwn(source, field.key)) {
|
|
162
230
|
throw new TypeError(`${name}.from: missing field '${field.key}'.`);
|
|
@@ -172,6 +240,7 @@ const struct = (name, fields) => {
|
|
|
172
240
|
},
|
|
173
241
|
is: (value) => typeof value === 'object' &&
|
|
174
242
|
value !== null &&
|
|
243
|
+
(prototype === undefined || Object.getPrototypeOf(value) === prototype) &&
|
|
175
244
|
Object.isFrozen(value) &&
|
|
176
245
|
Object.keys(value).length === plan.length &&
|
|
177
246
|
plan.every((field) => Object.hasOwn(value, field.key) &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fulcro/types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Numeric types with a defined range and layout: fixed-width integers, half, single and double precision floats, and a decimal128 Decimal.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"integer",
|
|
@@ -19,35 +19,12 @@
|
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"default": "./dist/index.js"
|
|
21
21
|
},
|
|
22
|
-
"./transformer": {
|
|
23
|
-
"types": "./dist/transformer/index.d.ts",
|
|
24
|
-
"default": "./dist/transformer/index.js"
|
|
25
|
-
},
|
|
26
|
-
"./unplugin": {
|
|
27
|
-
"types": "./dist/unplugin/index.d.mts",
|
|
28
|
-
"default": "./dist/unplugin/index.mjs"
|
|
29
|
-
},
|
|
30
|
-
"./language-service": {
|
|
31
|
-
"types": "./dist/languageService/index.d.ts",
|
|
32
|
-
"default": "./dist/languageService/index.js"
|
|
33
|
-
},
|
|
34
22
|
"./package.json": "./package.json"
|
|
35
23
|
},
|
|
36
24
|
"files": [
|
|
37
25
|
"dist"
|
|
38
26
|
],
|
|
39
27
|
"sideEffects": false,
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@fulcro/transform-core": "^0.10.0"
|
|
42
|
-
},
|
|
43
|
-
"peerDependencies": {
|
|
44
|
-
"typescript": ">=5.3.3 <7"
|
|
45
|
-
},
|
|
46
|
-
"peerDependenciesMeta": {
|
|
47
|
-
"typescript": {
|
|
48
|
-
"optional": true
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
28
|
"engines": {
|
|
52
29
|
"node": ">=22"
|
|
53
30
|
},
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type LanguageServicePlugin } from '@fulcro/transform-core';
|
|
2
|
-
/**
|
|
3
|
-
* The editor plugin giving the operators their meaning on this package's
|
|
4
|
-
* numeric types: the types, the hover, the completions and the errors an editor
|
|
5
|
-
* shows are those of the rewritten code, mapped back onto the code as written.
|
|
6
|
-
*
|
|
7
|
-
* ```json
|
|
8
|
-
* {
|
|
9
|
-
* "compilerOptions": {
|
|
10
|
-
* "plugins": [{ "name": "@fulcro/types/language-service" }]
|
|
11
|
-
* }
|
|
12
|
-
* }
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* VS Code loads it only from the workspace's own TypeScript: select it with
|
|
16
|
-
* **TypeScript: Select TypeScript Version → Use Workspace Version**. An editor
|
|
17
|
-
* that does not load it shows what `tsc` alone would — the operators on a
|
|
18
|
-
* `Decimal` underlined, the others typed as `number`.
|
|
19
|
-
*/
|
|
20
|
-
declare const plugin: LanguageServicePlugin;
|
|
21
|
-
export = plugin;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const transform_core_1 = require("@fulcro/transform-core");
|
|
3
|
-
const rewriter_1 = require("../transformer/rewriter/index.js");
|
|
4
|
-
/**
|
|
5
|
-
* The editor plugin giving the operators their meaning on this package's
|
|
6
|
-
* numeric types: the types, the hover, the completions and the errors an editor
|
|
7
|
-
* shows are those of the rewritten code, mapped back onto the code as written.
|
|
8
|
-
*
|
|
9
|
-
* ```json
|
|
10
|
-
* {
|
|
11
|
-
* "compilerOptions": {
|
|
12
|
-
* "plugins": [{ "name": "@fulcro/types/language-service" }]
|
|
13
|
-
* }
|
|
14
|
-
* }
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* VS Code loads it only from the workspace's own TypeScript: select it with
|
|
18
|
-
* **TypeScript: Select TypeScript Version → Use Workspace Version**. An editor
|
|
19
|
-
* that does not load it shows what `tsc` alone would — the operators on a
|
|
20
|
-
* `Decimal` underlined, the others typed as `number`.
|
|
21
|
-
*/
|
|
22
|
-
const plugin = (0, transform_core_1.createLanguageServicePlugin)(rewriter_1.OPERATOR_REWRITER);
|
|
23
|
-
module.exports = plugin;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import typescript from 'typescript';
|
|
2
|
-
/**
|
|
3
|
-
* Which of this package's numeric types a checker type is, if any.
|
|
4
|
-
*
|
|
5
|
-
* The rewrite of the operators must claim only this package's types. A
|
|
6
|
-
* consumer's own `number` and `bigint` arithmetic is none of its business, and
|
|
7
|
-
* a brand that merely looks like ours — a property with the same name in some
|
|
8
|
-
* other library — is not ours either. So a type is recognised by where its
|
|
9
|
-
* brand was **declared**: the `brand` module of this package, as source in this
|
|
10
|
-
* repository and as `dist` in a consumer's `node_modules`.
|
|
11
|
-
*/
|
|
12
|
-
/** How the operators of one numeric type are written out. */
|
|
13
|
-
export type NumericKind = {
|
|
14
|
-
/** Called through a descriptor: `SignedInteger(32).add(a, b)`. */
|
|
15
|
-
readonly family: 'descriptor';
|
|
16
|
-
/** The descriptor, as an expression under the namespace import. */
|
|
17
|
-
readonly descriptor: string;
|
|
18
|
-
/** Whether the bit operators apply. */
|
|
19
|
-
readonly integer: boolean;
|
|
20
|
-
/** Name of the brand, which tells two kinds apart. */
|
|
21
|
-
readonly name: string;
|
|
22
|
-
} | {
|
|
23
|
-
/** Called as methods of the value: `a.add(b)`. */
|
|
24
|
-
readonly family: 'decimal';
|
|
25
|
-
readonly name: 'Decimal';
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Classifies a type.
|
|
29
|
-
*
|
|
30
|
-
* A union is never one of ours, even a union of our types: `SignedInteger<8> |
|
|
31
|
-
* SignedInteger<16>` has no one descriptor to call, and `Decimal | undefined`
|
|
32
|
-
* has to be narrowed first, which the checker already insists on.
|
|
33
|
-
*
|
|
34
|
-
* @param type Type to classify.
|
|
35
|
-
* @param checker Checker of the program.
|
|
36
|
-
* @param location Node the type was read at.
|
|
37
|
-
* @returns The kind, or `null` when the type is not one of this package's.
|
|
38
|
-
*/
|
|
39
|
-
export declare const classify: (type: typescript.Type, checker: typescript.TypeChecker, location: typescript.Node) => NumericKind | null;
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.classify = void 0;
|
|
4
|
-
/** The module declaring the brand every primitive-backed type carries. */
|
|
5
|
-
const BRAND_MODULE = /\/types\/(?:dist|src)\/brand\/index\.(?:d\.)?ts$/;
|
|
6
|
-
/** The module declaring `Decimal`. */
|
|
7
|
-
const DECIMAL_MODULE = /\/types\/(?:dist|src)\/decimal\/index\.(?:d\.)?ts$/;
|
|
8
|
-
/** A brand of a fixed-width integer, and its parts. */
|
|
9
|
-
const INTEGER_BRAND = /^(Signed|Unsigned)Integer(8|16|32|64|128)$/;
|
|
10
|
-
/** Brands whose descriptor is a value of the same name. */
|
|
11
|
-
const NAMED_BRANDS = new Set([
|
|
12
|
-
'HalfPrecisionFloat',
|
|
13
|
-
'SinglePrecisionFloat',
|
|
14
|
-
'DoublePrecisionFloat',
|
|
15
|
-
'BigInteger',
|
|
16
|
-
]);
|
|
17
|
-
/**
|
|
18
|
-
* Tells whether a declaration was made in a module of this package.
|
|
19
|
-
*
|
|
20
|
-
* @param declaration Declaration of a symbol.
|
|
21
|
-
* @param module Pattern of the module.
|
|
22
|
-
* @returns `true` when it was.
|
|
23
|
-
*/
|
|
24
|
-
const declaredIn = (declaration, module) => module.test(declaration.getSourceFile().fileName.replace(/\\/g, '/'));
|
|
25
|
-
/**
|
|
26
|
-
* The kind a brand stands for.
|
|
27
|
-
*
|
|
28
|
-
* @param brand Value of the brand property.
|
|
29
|
-
* @returns The kind, or `null` for a brand this rewrite does not know.
|
|
30
|
-
*/
|
|
31
|
-
const kindOfBrand = (brand) => {
|
|
32
|
-
const integer = INTEGER_BRAND.exec(brand);
|
|
33
|
-
if (integer !== null) {
|
|
34
|
-
return {
|
|
35
|
-
family: 'descriptor',
|
|
36
|
-
descriptor: `${integer[1]}Integer(${integer[2]})`,
|
|
37
|
-
integer: true,
|
|
38
|
-
name: brand,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
if (NAMED_BRANDS.has(brand)) {
|
|
42
|
-
return {
|
|
43
|
-
family: 'descriptor',
|
|
44
|
-
descriptor: brand,
|
|
45
|
-
integer: false,
|
|
46
|
-
name: brand,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Classifies a type.
|
|
53
|
-
*
|
|
54
|
-
* A union is never one of ours, even a union of our types: `SignedInteger<8> |
|
|
55
|
-
* SignedInteger<16>` has no one descriptor to call, and `Decimal | undefined`
|
|
56
|
-
* has to be narrowed first, which the checker already insists on.
|
|
57
|
-
*
|
|
58
|
-
* @param type Type to classify.
|
|
59
|
-
* @param checker Checker of the program.
|
|
60
|
-
* @param location Node the type was read at.
|
|
61
|
-
* @returns The kind, or `null` when the type is not one of this package's.
|
|
62
|
-
*/
|
|
63
|
-
const classify = (type, checker, location) => {
|
|
64
|
-
if (type.isUnion())
|
|
65
|
-
return null;
|
|
66
|
-
const symbol = type.getSymbol();
|
|
67
|
-
if (symbol?.getName() === 'Decimal' &&
|
|
68
|
-
(symbol.declarations ?? []).some((declaration) => declaredIn(declaration, DECIMAL_MODULE))) {
|
|
69
|
-
return { family: 'decimal', name: 'Decimal' };
|
|
70
|
-
}
|
|
71
|
-
for (const property of checker.getPropertiesOfType(type)) {
|
|
72
|
-
// The brand is keyed by a unique symbol, which the checker names
|
|
73
|
-
// `__@brand@<id>`; the declaration settles whose it is.
|
|
74
|
-
if (!String(property.escapedName).startsWith('__@'))
|
|
75
|
-
continue;
|
|
76
|
-
const ours = (property.declarations ?? []).some((declaration) => declaredIn(declaration, BRAND_MODULE));
|
|
77
|
-
if (!ours)
|
|
78
|
-
continue;
|
|
79
|
-
const brand = checker.getTypeOfSymbolAtLocation(property, location);
|
|
80
|
-
return brand.isStringLiteral() ? kindOfBrand(brand.value) : null;
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
83
|
-
};
|
|
84
|
-
exports.classify = classify;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { type ProgramTransformer } from '@fulcro/transform-core';
|
|
2
|
-
/**
|
|
3
|
-
* The `tsc` plugin giving the operators their meaning on this package's
|
|
4
|
-
* numeric types.
|
|
5
|
-
*
|
|
6
|
-
* A program transformer, not an ordinary one, and the tsconfig entry has to say
|
|
7
|
-
* so: the rewrite must happen before the program is type checked, or
|
|
8
|
-
* `decimal * decimal` has been reported as an error before anything could
|
|
9
|
-
* rewrite it. Wire it through `ts-patch`:
|
|
10
|
-
*
|
|
11
|
-
* ```json
|
|
12
|
-
* {
|
|
13
|
-
* "compilerOptions": {
|
|
14
|
-
* "plugins": [{ "transform": "@fulcro/types/transformer", "transformProgram": true }]
|
|
15
|
-
* }
|
|
16
|
-
* }
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* For a bundler use `@fulcro/types/unplugin`, and for the editor
|
|
20
|
-
* `@fulcro/types/language-service`.
|
|
21
|
-
*
|
|
22
|
-
* Without it, the operators are the language's own: a primitive-backed type
|
|
23
|
-
* does plain, unchecked arithmetic on its `number` or `bigint`, and a `Decimal`
|
|
24
|
-
* is refused by the checker and throws at runtime.
|
|
25
|
-
*/
|
|
26
|
-
declare const transformer: ProgramTransformer;
|
|
27
|
-
export default transformer;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const transform_core_1 = require("@fulcro/transform-core");
|
|
4
|
-
const rewriter_1 = require("./rewriter/index.js");
|
|
5
|
-
/**
|
|
6
|
-
* The `tsc` plugin giving the operators their meaning on this package's
|
|
7
|
-
* numeric types.
|
|
8
|
-
*
|
|
9
|
-
* A program transformer, not an ordinary one, and the tsconfig entry has to say
|
|
10
|
-
* so: the rewrite must happen before the program is type checked, or
|
|
11
|
-
* `decimal * decimal` has been reported as an error before anything could
|
|
12
|
-
* rewrite it. Wire it through `ts-patch`:
|
|
13
|
-
*
|
|
14
|
-
* ```json
|
|
15
|
-
* {
|
|
16
|
-
* "compilerOptions": {
|
|
17
|
-
* "plugins": [{ "transform": "@fulcro/types/transformer", "transformProgram": true }]
|
|
18
|
-
* }
|
|
19
|
-
* }
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* For a bundler use `@fulcro/types/unplugin`, and for the editor
|
|
23
|
-
* `@fulcro/types/language-service`.
|
|
24
|
-
*
|
|
25
|
-
* Without it, the operators are the language's own: a primitive-backed type
|
|
26
|
-
* does plain, unchecked arithmetic on its `number` or `bigint`, and a `Decimal`
|
|
27
|
-
* is refused by the checker and throws at runtime.
|
|
28
|
-
*/
|
|
29
|
-
const transformer = (0, transform_core_1.createProgramTransformer)(rewriter_1.OPERATOR_REWRITER);
|
|
30
|
-
exports.default = transformer;
|
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OPERATOR_REWRITER = void 0;
|
|
7
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
-
const classify_1 = require("../classify/index.js");
|
|
9
|
-
/**
|
|
10
|
-
* The rewrite of the JavaScript operators on this package's numeric types.
|
|
11
|
-
*
|
|
12
|
-
* Every operator becomes a call to the operation that means it for the type —
|
|
13
|
-
* `a + b` on two `SignedInteger<32>` becomes `SignedInteger(32).add(a, b)`,
|
|
14
|
-
* checked for overflow; on two `Decimal`, `a.add(b)`. The call is what the
|
|
15
|
-
* checker then sees, so the result keeps its type, and an operand of any other
|
|
16
|
-
* type fails to type check as an argument: that is how "the same type only" is
|
|
17
|
-
* enforced, with the checker's own message at the site.
|
|
18
|
-
*
|
|
19
|
-
* | Written | Becomes |
|
|
20
|
-
* | ------------------------------------------------ | ------------------------------ |
|
|
21
|
-
* | `a + b`, `-`, `*`, `/`, `%`, `**` | `T.add(a, b)` … |
|
|
22
|
-
* | `a & b`, `\|`, `^`, `<<`, `>>`, `>>>` | `T.bitwiseAnd(a, b)` … |
|
|
23
|
-
* | `a < b`, `<=`, `>`, `>=` | `T.lessThan(a, b)` … |
|
|
24
|
-
* | `a === b`, `==`, `!==`, `!=` | `T.equals(a, b)`, negated |
|
|
25
|
-
* | `-a`, `+a`, `~a` | `T.negate(a)`, `a`, `T.bitwiseNot(a)` |
|
|
26
|
-
* | `++a`, `a++`, `--a`, `a--` | `T.increment` / `T.decrement` |
|
|
27
|
-
* | `a += b` and every compound assignment | `a = T.add(a, b)` … |
|
|
28
|
-
*
|
|
29
|
-
* Evaluation order and the value of each expression are those of the operator
|
|
30
|
-
* it replaces: a compound assignment evaluates its target once, and a postfix
|
|
31
|
-
* operator evaluates to the value before the change.
|
|
32
|
-
*/
|
|
33
|
-
/** Module every rewritten file imports, and the name it is imported under. */
|
|
34
|
-
const MODULE = '@fulcro/types';
|
|
35
|
-
const NAMESPACE = '__fulcroTypes';
|
|
36
|
-
/** The operation each binary operator stands for. */
|
|
37
|
-
const BINARY_OPERATIONS = new Map([
|
|
38
|
-
[typescript_1.default.SyntaxKind.PlusToken, 'add'],
|
|
39
|
-
[typescript_1.default.SyntaxKind.MinusToken, 'subtract'],
|
|
40
|
-
[typescript_1.default.SyntaxKind.AsteriskToken, 'multiply'],
|
|
41
|
-
[typescript_1.default.SyntaxKind.SlashToken, 'divide'],
|
|
42
|
-
[typescript_1.default.SyntaxKind.PercentToken, 'remainder'],
|
|
43
|
-
[typescript_1.default.SyntaxKind.AsteriskAsteriskToken, 'power'],
|
|
44
|
-
[typescript_1.default.SyntaxKind.AmpersandToken, 'bitwiseAnd'],
|
|
45
|
-
[typescript_1.default.SyntaxKind.BarToken, 'bitwiseOr'],
|
|
46
|
-
[typescript_1.default.SyntaxKind.CaretToken, 'bitwiseXor'],
|
|
47
|
-
[typescript_1.default.SyntaxKind.LessThanLessThanToken, 'shiftLeft'],
|
|
48
|
-
[typescript_1.default.SyntaxKind.GreaterThanGreaterThanToken, 'shiftRight'],
|
|
49
|
-
[
|
|
50
|
-
typescript_1.default.SyntaxKind.GreaterThanGreaterThanGreaterThanToken,
|
|
51
|
-
'shiftRightLogical',
|
|
52
|
-
],
|
|
53
|
-
[typescript_1.default.SyntaxKind.LessThanToken, 'lessThan'],
|
|
54
|
-
[typescript_1.default.SyntaxKind.LessThanEqualsToken, 'lessThanOrEqual'],
|
|
55
|
-
[typescript_1.default.SyntaxKind.GreaterThanToken, 'greaterThan'],
|
|
56
|
-
[typescript_1.default.SyntaxKind.GreaterThanEqualsToken, 'greaterThanOrEqual'],
|
|
57
|
-
]);
|
|
58
|
-
/** The operation each compound assignment applies before assigning. */
|
|
59
|
-
const COMPOUND_OPERATIONS = new Map([
|
|
60
|
-
[typescript_1.default.SyntaxKind.PlusEqualsToken, 'add'],
|
|
61
|
-
[typescript_1.default.SyntaxKind.MinusEqualsToken, 'subtract'],
|
|
62
|
-
[typescript_1.default.SyntaxKind.AsteriskEqualsToken, 'multiply'],
|
|
63
|
-
[typescript_1.default.SyntaxKind.SlashEqualsToken, 'divide'],
|
|
64
|
-
[typescript_1.default.SyntaxKind.PercentEqualsToken, 'remainder'],
|
|
65
|
-
[typescript_1.default.SyntaxKind.AsteriskAsteriskEqualsToken, 'power'],
|
|
66
|
-
[typescript_1.default.SyntaxKind.AmpersandEqualsToken, 'bitwiseAnd'],
|
|
67
|
-
[typescript_1.default.SyntaxKind.BarEqualsToken, 'bitwiseOr'],
|
|
68
|
-
[typescript_1.default.SyntaxKind.CaretEqualsToken, 'bitwiseXor'],
|
|
69
|
-
[typescript_1.default.SyntaxKind.LessThanLessThanEqualsToken, 'shiftLeft'],
|
|
70
|
-
[typescript_1.default.SyntaxKind.GreaterThanGreaterThanEqualsToken, 'shiftRight'],
|
|
71
|
-
[
|
|
72
|
-
typescript_1.default.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
|
|
73
|
-
'shiftRightLogical',
|
|
74
|
-
],
|
|
75
|
-
]);
|
|
76
|
-
/** Equality operators, and whether each is negated. */
|
|
77
|
-
const EQUALITY = new Map([
|
|
78
|
-
[typescript_1.default.SyntaxKind.EqualsEqualsEqualsToken, false],
|
|
79
|
-
[typescript_1.default.SyntaxKind.EqualsEqualsToken, false],
|
|
80
|
-
[typescript_1.default.SyntaxKind.ExclamationEqualsEqualsToken, true],
|
|
81
|
-
[typescript_1.default.SyntaxKind.ExclamationEqualsToken, true],
|
|
82
|
-
]);
|
|
83
|
-
/**
|
|
84
|
-
* The call applying an operation to operands, in the form the kind uses.
|
|
85
|
-
*
|
|
86
|
-
* @param kind Kind the operation belongs to.
|
|
87
|
-
* @param operation Name of the operation.
|
|
88
|
-
* @param operands Its operands, in order.
|
|
89
|
-
* @param separator What goes between two operands, line breaks included.
|
|
90
|
-
* @param leftIsOurs Whether the first operand is of the kind — for a decimal,
|
|
91
|
-
* whether the method can be called on it.
|
|
92
|
-
* @returns The replacement.
|
|
93
|
-
*/
|
|
94
|
-
const call = (kind, operation, operands, separator = ', ', leftIsOurs = true) => {
|
|
95
|
-
if (kind.family === 'descriptor') {
|
|
96
|
-
return [
|
|
97
|
-
`${NAMESPACE}.${kind.descriptor}.${operation}(`,
|
|
98
|
-
...interleave(operands, separator),
|
|
99
|
-
')',
|
|
100
|
-
];
|
|
101
|
-
}
|
|
102
|
-
const [receiver, ...rest] = operands;
|
|
103
|
-
if (!leftIsOurs) {
|
|
104
|
-
// The method cannot be called on an operand that is not a decimal, and
|
|
105
|
-
// the checker has to say so at the site: a function taking two
|
|
106
|
-
// decimals makes it.
|
|
107
|
-
return [
|
|
108
|
-
`((__left: ${NAMESPACE}.Decimal, __right: ${NAMESPACE}.Decimal) => __left.${operation}(__right))(`,
|
|
109
|
-
...interleave(operands, separator),
|
|
110
|
-
')',
|
|
111
|
-
];
|
|
112
|
-
}
|
|
113
|
-
return [
|
|
114
|
-
'(',
|
|
115
|
-
receiver,
|
|
116
|
-
`).${operation}(`,
|
|
117
|
-
...interleave(rest, separator),
|
|
118
|
-
')',
|
|
119
|
-
];
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* Puts a separator between operands.
|
|
123
|
-
*
|
|
124
|
-
* @param operands Operands, in order.
|
|
125
|
-
* @param separator What goes between two of them.
|
|
126
|
-
* @returns The operands with separators.
|
|
127
|
-
*/
|
|
128
|
-
const interleave = (operands, separator) => operands.flatMap((operand, index) => index === 0 ? [operand] : [separator, operand]);
|
|
129
|
-
/**
|
|
130
|
-
* One, as the kind writes it, for `++` and `--` on a decimal.
|
|
131
|
-
*
|
|
132
|
-
* @returns The expression.
|
|
133
|
-
*/
|
|
134
|
-
const decimalOne = () => `${NAMESPACE}.Decimal.from(1)`;
|
|
135
|
-
/**
|
|
136
|
-
* The increment or decrement of an operand, as the kind computes it.
|
|
137
|
-
*
|
|
138
|
-
* @param kind Kind of the operand.
|
|
139
|
-
* @param increment Whether it goes up.
|
|
140
|
-
* @param operand The operand.
|
|
141
|
-
* @returns The replacement.
|
|
142
|
-
*/
|
|
143
|
-
const step = (kind, increment, operand) => kind.family === 'descriptor'
|
|
144
|
-
? call(kind, increment ? 'increment' : 'decrement', [operand])
|
|
145
|
-
: call(kind, increment ? 'add' : 'subtract', [operand, decimalOne()]);
|
|
146
|
-
/**
|
|
147
|
-
* Strips the parentheses around an expression.
|
|
148
|
-
*
|
|
149
|
-
* @param expression Expression, possibly parenthesized.
|
|
150
|
-
* @returns The expression inside.
|
|
151
|
-
*/
|
|
152
|
-
const unwrap = (expression) => typescript_1.default.isParenthesizedExpression(expression)
|
|
153
|
-
? unwrap(expression.expression)
|
|
154
|
-
: expression;
|
|
155
|
-
/**
|
|
156
|
-
* Tells whether an expression can be evaluated twice without anything
|
|
157
|
-
* observable happening: a name, `this`, or a literal.
|
|
158
|
-
*
|
|
159
|
-
* @param expression Expression to inspect.
|
|
160
|
-
* @returns `true` when evaluating it again is harmless.
|
|
161
|
-
*/
|
|
162
|
-
const isInert = (expression) => typescript_1.default.isIdentifier(expression) ||
|
|
163
|
-
expression.kind === typescript_1.default.SyntaxKind.ThisKeyword ||
|
|
164
|
-
typescript_1.default.isLiteralExpression(expression);
|
|
165
|
-
/**
|
|
166
|
-
* How an assignment target is written back to, evaluating its parts once.
|
|
167
|
-
*
|
|
168
|
-
* A name, or a member of a name, is written twice as it stands. Any other
|
|
169
|
-
* member — `items[next()]`, `load().total` — is evaluated once into the
|
|
170
|
-
* parameters of an arrow function called on the spot, which is also what keeps
|
|
171
|
-
* the order of evaluation JavaScript's.
|
|
172
|
-
*
|
|
173
|
-
* @param target The target, parentheses stripped.
|
|
174
|
-
* @returns How to wrap an assignment to it.
|
|
175
|
-
*/
|
|
176
|
-
const assignmentForm = (target) => {
|
|
177
|
-
if (typescript_1.default.isPropertyAccessExpression(target) &&
|
|
178
|
-
!isInert(target.expression)) {
|
|
179
|
-
return {
|
|
180
|
-
reference: `__target.${target.name.text}`,
|
|
181
|
-
open: ['((__target) => ('],
|
|
182
|
-
close: ['))(', target.expression, ')'],
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
if (typescript_1.default.isElementAccessExpression(target) &&
|
|
186
|
-
!(isInert(target.expression) && isInert(target.argumentExpression))) {
|
|
187
|
-
return {
|
|
188
|
-
reference: '__target[__key]',
|
|
189
|
-
open: ['((__target, __key) => ('],
|
|
190
|
-
close: ['))(', target.expression, ', ', target.argumentExpression, ')'],
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
return { reference: target, open: ['('], close: [')'] };
|
|
194
|
-
};
|
|
195
|
-
/**
|
|
196
|
-
* Tells whether a type is one a string concatenation would produce, which
|
|
197
|
-
* `+` then means instead of addition.
|
|
198
|
-
*
|
|
199
|
-
* @param type Type of an operand.
|
|
200
|
-
* @returns `true` for a string.
|
|
201
|
-
*/
|
|
202
|
-
const isString = (type) => (type.flags & typescript_1.default.TypeFlags.StringLike) !== 0;
|
|
203
|
-
/**
|
|
204
|
-
* Tells whether a type is a plain number or bigint — the kind of operand an
|
|
205
|
-
* equality with one of ours has to refuse, rather than leave to a comparison
|
|
206
|
-
* that would quietly succeed.
|
|
207
|
-
*
|
|
208
|
-
* @param type Type of an operand.
|
|
209
|
-
* @returns `true` for a number or a bigint.
|
|
210
|
-
*/
|
|
211
|
-
const isNumeric = (type) => (type.flags &
|
|
212
|
-
(typescript_1.default.TypeFlags.NumberLike | typescript_1.default.TypeFlags.BigIntLike)) !==
|
|
213
|
-
0;
|
|
214
|
-
/**
|
|
215
|
-
* Rewrites a binary expression.
|
|
216
|
-
*
|
|
217
|
-
* @param node The expression.
|
|
218
|
-
* @param context The file and its checker.
|
|
219
|
-
* @returns The replacement, or `null`.
|
|
220
|
-
*/
|
|
221
|
-
const rewriteBinary = (node, context) => {
|
|
222
|
-
const { checker } = context;
|
|
223
|
-
const operator = node.operatorToken.kind;
|
|
224
|
-
const leftType = checker.getTypeAtLocation(node.left);
|
|
225
|
-
const rightType = checker.getTypeAtLocation(node.right);
|
|
226
|
-
const left = (0, classify_1.classify)(leftType, checker, node.left);
|
|
227
|
-
const right = (0, classify_1.classify)(rightType, checker, node.right);
|
|
228
|
-
const kind = left ?? right;
|
|
229
|
-
if (kind === null)
|
|
230
|
-
return null;
|
|
231
|
-
const separator = `, ${context.lineBreaks(node.left.end, node.right.getStart(context.sourceFile))}`;
|
|
232
|
-
const compound = COMPOUND_OPERATIONS.get(operator);
|
|
233
|
-
if (compound !== undefined) {
|
|
234
|
-
const target = unwrap(node.left);
|
|
235
|
-
const form = assignmentForm(target);
|
|
236
|
-
return [
|
|
237
|
-
...form.open,
|
|
238
|
-
form.reference,
|
|
239
|
-
' = ',
|
|
240
|
-
...call(kind, compound, [form.reference, node.right], separator, left !== null),
|
|
241
|
-
...form.close,
|
|
242
|
-
];
|
|
243
|
-
}
|
|
244
|
-
const negated = EQUALITY.get(operator);
|
|
245
|
-
if (negated !== undefined) {
|
|
246
|
-
// An equality with something that is not a number at all — `null`, an
|
|
247
|
-
// object — is a question about identity, and stays one.
|
|
248
|
-
const comparable = (left !== null || isNumeric(leftType)) &&
|
|
249
|
-
(right !== null || isNumeric(rightType));
|
|
250
|
-
if (!comparable)
|
|
251
|
-
return null;
|
|
252
|
-
const equals = call(kind, 'equals', [node.left, node.right], separator, left !== null);
|
|
253
|
-
return negated ? ['(!', ...equals, ')'] : equals;
|
|
254
|
-
}
|
|
255
|
-
const operation = BINARY_OPERATIONS.get(operator);
|
|
256
|
-
if (operation === undefined)
|
|
257
|
-
return null;
|
|
258
|
-
if (operator === typescript_1.default.SyntaxKind.PlusToken &&
|
|
259
|
-
(isString(leftType) || isString(rightType))) {
|
|
260
|
-
// Concatenation, not addition. A primitive-backed value concatenates as
|
|
261
|
-
// a number does; a decimal refuses the implicit conversion, so its text
|
|
262
|
-
// is asked for explicitly.
|
|
263
|
-
if (kind.family !== 'decimal')
|
|
264
|
-
return null;
|
|
265
|
-
const text = (operand, ours) => ours ? ['(', operand, ').toString()'] : [operand];
|
|
266
|
-
return [
|
|
267
|
-
...text(node.left, left !== null),
|
|
268
|
-
` + ${context.lineBreaks(node.left.end, node.right.getStart(context.sourceFile))}`,
|
|
269
|
-
...text(node.right, right !== null),
|
|
270
|
-
];
|
|
271
|
-
}
|
|
272
|
-
return call(kind, operation, [node.left, node.right], separator, left !== null);
|
|
273
|
-
};
|
|
274
|
-
/**
|
|
275
|
-
* Rewrites an increment or a decrement, written before or after its operand.
|
|
276
|
-
*
|
|
277
|
-
* @param node The expression.
|
|
278
|
-
* @param operand Its operand.
|
|
279
|
-
* @param increment Whether it goes up.
|
|
280
|
-
* @param postfix Whether it was written after the operand.
|
|
281
|
-
* @param context The file and its checker.
|
|
282
|
-
* @returns The replacement, or `null`.
|
|
283
|
-
*/
|
|
284
|
-
const rewriteStep = (node, operand, increment, postfix, context) => {
|
|
285
|
-
const kind = (0, classify_1.classify)(context.checker.getTypeAtLocation(operand), context.checker, operand);
|
|
286
|
-
if (kind === null)
|
|
287
|
-
return null;
|
|
288
|
-
const target = unwrap(operand);
|
|
289
|
-
const form = assignmentForm(target);
|
|
290
|
-
// Where the value of the expression is thrown away — a statement of its
|
|
291
|
-
// own, the step of a `for` — the prefix form does the same work.
|
|
292
|
-
const valueUnused = typescript_1.default.isExpressionStatement(node.parent) ||
|
|
293
|
-
(typescript_1.default.isForStatement(node.parent) &&
|
|
294
|
-
node.parent.incrementor === node);
|
|
295
|
-
if (!postfix || valueUnused) {
|
|
296
|
-
return [
|
|
297
|
-
...form.open,
|
|
298
|
-
form.reference,
|
|
299
|
-
' = ',
|
|
300
|
-
...step(kind, increment, form.reference),
|
|
301
|
-
...form.close,
|
|
302
|
-
];
|
|
303
|
-
}
|
|
304
|
-
// The value before the step is taken once, as a default parameter, so the
|
|
305
|
-
// target is read once and the expression still evaluates to it.
|
|
306
|
-
if (typeof form.reference === 'string') {
|
|
307
|
-
const [head] = form.open;
|
|
308
|
-
const parameters = head.replace(') => (', `, __previous = ${form.reference}) => (`);
|
|
309
|
-
return [
|
|
310
|
-
parameters,
|
|
311
|
-
'(',
|
|
312
|
-
form.reference,
|
|
313
|
-
' = ',
|
|
314
|
-
...step(kind, increment, '__previous'),
|
|
315
|
-
'), __previous',
|
|
316
|
-
...form.close,
|
|
317
|
-
];
|
|
318
|
-
}
|
|
319
|
-
return [
|
|
320
|
-
'((__previous) => ((',
|
|
321
|
-
target,
|
|
322
|
-
' = ',
|
|
323
|
-
...step(kind, increment, '__previous'),
|
|
324
|
-
'), __previous))(',
|
|
325
|
-
target,
|
|
326
|
-
')',
|
|
327
|
-
];
|
|
328
|
-
};
|
|
329
|
-
/**
|
|
330
|
-
* Rewrites a unary expression written before its operand.
|
|
331
|
-
*
|
|
332
|
-
* @param node The expression.
|
|
333
|
-
* @param context The file and its checker.
|
|
334
|
-
* @returns The replacement, or `null`.
|
|
335
|
-
*/
|
|
336
|
-
const rewritePrefix = (node, context) => {
|
|
337
|
-
switch (node.operator) {
|
|
338
|
-
case typescript_1.default.SyntaxKind.PlusPlusToken:
|
|
339
|
-
return rewriteStep(node, node.operand, true, false, context);
|
|
340
|
-
case typescript_1.default.SyntaxKind.MinusMinusToken:
|
|
341
|
-
return rewriteStep(node, node.operand, false, false, context);
|
|
342
|
-
}
|
|
343
|
-
const kind = (0, classify_1.classify)(context.checker.getTypeAtLocation(node.operand), context.checker, node.operand);
|
|
344
|
-
if (kind === null)
|
|
345
|
-
return null;
|
|
346
|
-
switch (node.operator) {
|
|
347
|
-
case typescript_1.default.SyntaxKind.MinusToken:
|
|
348
|
-
return call(kind, 'negate', [node.operand]);
|
|
349
|
-
case typescript_1.default.SyntaxKind.TildeToken:
|
|
350
|
-
return call(kind, 'bitwiseNot', [node.operand]);
|
|
351
|
-
case typescript_1.default.SyntaxKind.PlusToken:
|
|
352
|
-
// The identity, which on a `number` would still widen the type.
|
|
353
|
-
return ['(', node.operand, ')'];
|
|
354
|
-
default:
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
/** The rewriter of the operators on this package's numeric types. */
|
|
359
|
-
exports.OPERATOR_REWRITER = {
|
|
360
|
-
module: MODULE,
|
|
361
|
-
namespace: NAMESPACE,
|
|
362
|
-
rewrite: (node, context) => {
|
|
363
|
-
if (typescript_1.default.isBinaryExpression(node)) {
|
|
364
|
-
return rewriteBinary(node, context);
|
|
365
|
-
}
|
|
366
|
-
if (typescript_1.default.isPrefixUnaryExpression(node)) {
|
|
367
|
-
return rewritePrefix(node, context);
|
|
368
|
-
}
|
|
369
|
-
if (typescript_1.default.isPostfixUnaryExpression(node)) {
|
|
370
|
-
return rewriteStep(node, node.operand, node.operator === typescript_1.default.SyntaxKind.PlusPlusToken, true, context);
|
|
371
|
-
}
|
|
372
|
-
return null;
|
|
373
|
-
},
|
|
374
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export type { PluginOptions } from '@fulcro/transform-core/unplugin';
|
|
2
|
-
/** The factory itself, for a bundler not covered below. */
|
|
3
|
-
export declare const unpluginFactory: import("unplugin").UnpluginFactory<import("@fulcro/transform-core").TransformCoreOptions | undefined>;
|
|
4
|
-
/** Adapter for Vite, which is also what vitest runs on. */
|
|
5
|
-
export declare const vite: (options?: import("@fulcro/transform-core").TransformCoreOptions | undefined) => import("vite").Plugin<any> | import("vite").Plugin<any>[];
|
|
6
|
-
/** Adapter for Rollup. */
|
|
7
|
-
export declare const rollup: (options?: import("@fulcro/transform-core").TransformCoreOptions | undefined) => any;
|
|
8
|
-
/** Adapter for Webpack. */
|
|
9
|
-
export declare const webpack: (options?: import("@fulcro/transform-core").TransformCoreOptions | undefined) => WebpackPluginInstance;
|
|
10
|
-
/** Adapter for Rspack. */
|
|
11
|
-
export declare const rspack: (options?: import("@fulcro/transform-core").TransformCoreOptions | undefined) => RspackPluginInstance;
|
|
12
|
-
/** Adapter for esbuild. */
|
|
13
|
-
export declare const esbuild: (options?: import("@fulcro/transform-core").TransformCoreOptions | undefined) => EsbuildPlugin;
|
|
14
|
-
/** Adapter for Farm. */
|
|
15
|
-
export declare const farm: (options?: import("@fulcro/transform-core").TransformCoreOptions | undefined) => JsPlugin;
|
package/dist/unplugin/index.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { createRewriterUnplugin } from '@fulcro/transform-core/unplugin';
|
|
2
|
-
import { OPERATOR_REWRITER } from '../transformer/rewriter/index.js';
|
|
3
|
-
/**
|
|
4
|
-
* Bundler plugin giving the operators their meaning on this package's numeric
|
|
5
|
-
* types, for Vite, Rollup, Webpack, esbuild, Rspack and Farm:
|
|
6
|
-
*
|
|
7
|
-
* ```ts
|
|
8
|
-
* import { vite as fulcroTypes } from '@fulcro/types/unplugin';
|
|
9
|
-
*
|
|
10
|
-
* export default defineConfig({ plugins: [fulcroTypes()] });
|
|
11
|
-
* ```
|
|
12
|
-
*
|
|
13
|
-
* It runs before the bundler erases the types, and it reads the whole program:
|
|
14
|
-
* whether `c + d` in one file is ours depends on how `c` was declared in
|
|
15
|
-
* another.
|
|
16
|
-
*/
|
|
17
|
-
const plugins = createRewriterUnplugin(OPERATOR_REWRITER, 'fulcro-types');
|
|
18
|
-
/** The factory itself, for a bundler not covered below. */
|
|
19
|
-
export const unpluginFactory = plugins.unpluginFactory;
|
|
20
|
-
/** Adapter for Vite, which is also what vitest runs on. */
|
|
21
|
-
export const vite = plugins.vite;
|
|
22
|
-
/** Adapter for Rollup. */
|
|
23
|
-
export const rollup = plugins.rollup;
|
|
24
|
-
/** Adapter for Webpack. */
|
|
25
|
-
export const webpack = plugins.webpack;
|
|
26
|
-
/** Adapter for Rspack. */
|
|
27
|
-
export const rspack = plugins.rspack;
|
|
28
|
-
/** Adapter for esbuild. */
|
|
29
|
-
export const esbuild = plugins.esbuild;
|
|
30
|
-
/** Adapter for Farm. */
|
|
31
|
-
export const farm = plugins.farm;
|