@mapbox/mapbox-gl-style-spec 14.10.0 → 14.11.0-beta.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.
Files changed (46) hide show
  1. package/composite.ts +1 -0
  2. package/dist/index.cjs +467 -191
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +76 -39
  5. package/dist/index.es.js +467 -191
  6. package/dist/index.es.js.map +1 -1
  7. package/expression/compound_expression.ts +5 -7
  8. package/expression/definitions/at.ts +5 -19
  9. package/expression/definitions/at_interpolated.ts +80 -0
  10. package/expression/definitions/coercion.ts +1 -5
  11. package/expression/definitions/comparison.ts +2 -8
  12. package/expression/definitions/config.ts +1 -4
  13. package/expression/definitions/distance.ts +28 -43
  14. package/expression/definitions/format.ts +4 -9
  15. package/expression/definitions/image.ts +126 -73
  16. package/expression/definitions/index.ts +3 -2
  17. package/expression/definitions/interpolate.ts +28 -80
  18. package/expression/definitions/let.ts +2 -7
  19. package/expression/definitions/literal.ts +1 -2
  20. package/expression/definitions/match.ts +3 -12
  21. package/expression/definitions/step.ts +1 -5
  22. package/expression/evaluation_context.ts +3 -3
  23. package/expression/index.ts +12 -18
  24. package/expression/parsing_context.ts +3 -7
  25. package/expression/types/formatted.ts +7 -3
  26. package/expression/types/image_id.ts +61 -0
  27. package/expression/types/image_variant.ts +79 -0
  28. package/expression/types/resolved_image.ts +44 -53
  29. package/format.ts +1 -0
  30. package/function/index.ts +1 -0
  31. package/migrate/v8.ts +1 -0
  32. package/migrate/v9.ts +1 -0
  33. package/migrate.ts +1 -0
  34. package/package.json +1 -1
  35. package/read_style.ts +1 -0
  36. package/reference/v8.json +154 -7
  37. package/types/brand.ts +4 -0
  38. package/types.ts +30 -2
  39. package/util/color.ts +1 -1
  40. package/validate/validate.ts +6 -1
  41. package/validate/validate_iconset.ts +40 -0
  42. package/validate/validate_layer.ts +1 -5
  43. package/validate/validate_lights.ts +3 -2
  44. package/validate/validate_object.ts +2 -0
  45. package/validate/validate_style.ts +0 -1
  46. package/expression/types/image_id_with_options.ts +0 -58
@@ -1,56 +1,71 @@
1
- import {ColorType, ResolvedImageType, StringType} from '../types';
2
1
  import ResolvedImage from '../types/resolved_image';
2
+ import {ImageId} from '../types/image_id';
3
+ import {ColorType, ResolvedImageType, StringType} from '../types';
3
4
 
4
5
  import type Color from '../../util/color';
5
- import type {Expression, SerializedExpression} from '../expression';
6
- import type EvaluationContext from '../evaluation_context';
7
6
  import type ParsingContext from '../parsing_context';
7
+ import type EvaluationContext from '../evaluation_context';
8
8
  import type {Type} from '../types';
9
+ import type {Expression, SerializedExpression} from '../expression';
9
10
 
10
11
  export type ImageParams = Record<string, Expression>;
12
+ export type IconsetParams = {id: string};
11
13
 
12
14
  export type ImageOptions = {
13
- params: ImageParams;
15
+ params?: ImageParams;
16
+ iconset?: IconsetParams;
14
17
  }
15
18
 
16
- function isImageOptions(value: unknown) {
17
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
18
- return true;
19
- }
19
+ type SerializedImageOptions = {
20
+ params?: Record<string, SerializedExpression>;
21
+ iconset?: IconsetParams;
22
+ };
20
23
 
21
- return false;
24
+ function isImageOptions(value: unknown): value is ImageOptions {
25
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
22
26
  }
23
27
 
24
28
  export default class ImageExpression implements Expression {
25
29
  type: Type;
26
- inputPrimary: Expression;
27
- inputPrimaryParams: Record<string, Expression> | undefined;
28
- inputSecondary: Expression | null | undefined;
29
- inputSecondaryParams: Record<string, Expression> | undefined;
30
+
31
+ namePrimary: Expression;
32
+ paramsPrimary?: ImageParams;
33
+ iconsetIdPrimary?: string;
34
+
35
+ nameSecondary?: Expression;
36
+ paramsSecondary?: ImageParams;
37
+ iconsetIdSecondary?: string;
30
38
 
31
39
  _imageWarnHistory: Record<string, boolean> = {};
32
40
 
33
41
  constructor(
34
42
  inputPrimary: Expression,
35
43
  inputSecondary?: Expression | null,
36
- inputPrimaryParams?: Record<string, Expression>,
37
- inputSecondaryParams?: Record<string, Expression>
44
+ inputPrimaryOptions?: ImageOptions,
45
+ inputSecondaryOptions?: ImageOptions
38
46
  ) {
39
47
  this.type = ResolvedImageType;
40
- this.inputPrimary = inputPrimary;
41
- this.inputSecondary = inputSecondary;
42
- this.inputPrimaryParams = inputPrimaryParams;
43
- this.inputSecondaryParams = inputSecondaryParams;
48
+ this.namePrimary = inputPrimary;
49
+ this.nameSecondary = inputSecondary;
50
+
51
+ if (inputPrimaryOptions) {
52
+ this.paramsPrimary = inputPrimaryOptions.params;
53
+ this.iconsetIdPrimary = inputPrimaryOptions.iconset ? inputPrimaryOptions.iconset.id : undefined;
54
+ }
55
+
56
+ if (inputSecondaryOptions) {
57
+ this.paramsSecondary = inputSecondaryOptions.params;
58
+ this.iconsetIdSecondary = inputSecondaryOptions.iconset ? inputSecondaryOptions.iconset.id : undefined;
59
+ }
44
60
  }
45
61
 
46
- static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Expression | null | undefined {
62
+ static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Expression | null | void {
47
63
  if (args.length < 2) {
48
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Expression'.
49
64
  return context.error(`Expected two or more arguments.`);
50
65
  }
51
66
 
52
67
  let nextArgId = 1;
53
- const imageExpression: Array<{image: Expression, options: Record<string, Expression>}> = [];
68
+ const imageExpression: Array<{image: Expression, options?: ImageOptions}> = [];
54
69
 
55
70
  function tryParseImage() {
56
71
  if (nextArgId < args.length) {
@@ -60,7 +75,7 @@ export default class ImageExpression implements Expression {
60
75
  return false;
61
76
  }
62
77
 
63
- imageExpression.push({image: imageName, options: undefined});
78
+ imageExpression.push({image: imageName, options: {}});
64
79
  return true;
65
80
  }
66
81
 
@@ -69,45 +84,62 @@ export default class ImageExpression implements Expression {
69
84
 
70
85
  function tryParseOptions() {
71
86
  if (nextArgId < args.length) {
72
- if (!isImageOptions(args[nextArgId])) {
87
+ const options = args[nextArgId];
88
+ if (!isImageOptions(options)) {
73
89
  return true;
74
90
  }
75
91
 
76
- const params = (args[nextArgId] as ImageOptions).params;
77
-
92
+ const params = options.params;
93
+ const iconset = options.iconset;
78
94
  const optionsContext = context.concat(nextArgId);
79
95
 
80
- if (!params) {
96
+ if (!params && !iconset) {
81
97
  nextArgId++;
82
98
  return true;
83
99
  }
84
100
 
85
- if (typeof params !== 'object' || params.constructor !== Object) {
86
- optionsContext.error(`Image options \"params\" should be an object`);
87
- return false;
88
- }
101
+ // Parse the image options params as expressions
102
+ if (params) {
103
+ if (typeof params !== 'object' || params.constructor !== Object) {
104
+ optionsContext.error(`Image options \"params\" should be an object`);
105
+ return false;
106
+ }
89
107
 
90
- const parsed = {};
108
+ const parsedParams: ImageParams = {};
109
+ const childContext = optionsContext.concat(undefined, 'params');
110
+ for (const key in params) {
111
+ if (!key) {
112
+ childContext.error(`Image parameter name should be non-empty`);
113
+ return false;
114
+ }
91
115
 
92
- const childContext = optionsContext.concat(undefined, 'params');
116
+ const value = childContext.concat(undefined, key).parse(params[key], undefined, ColorType, undefined, {typeAnnotation: 'coerce'});
117
+ if (!value) {
118
+ return false;
119
+ }
93
120
 
94
- for (const key in params) {
95
- if (!key) {
96
- childContext.error(`Image parameter name should be non-empty`);
121
+ parsedParams[key] = value;
122
+ }
123
+
124
+ imageExpression[imageExpression.length - 1].options.params = parsedParams;
125
+ }
126
+
127
+ // Validate the iconset image options
128
+ if (iconset) {
129
+ if (typeof iconset !== 'object' || iconset.constructor !== Object) {
130
+ optionsContext.error(`Image options \"iconset\" should be an object`);
97
131
  return false;
98
132
  }
99
133
 
100
- const value = childContext.concat(undefined, key).parse(params[key], undefined, ColorType, undefined, {typeAnnotation: 'coerce'});
101
- if (!value) {
134
+ if (!iconset.id) {
135
+ optionsContext.error(`Image options \"iconset\" should have an \"id\" property`);
102
136
  return false;
103
137
  }
104
138
 
105
- parsed[key] = value;
139
+ imageExpression[imageExpression.length - 1].options.iconset = iconset;
106
140
  }
107
141
 
108
- imageExpression[imageExpression.length - 1].options = parsed;
109
142
  nextArgId++;
110
-
111
143
  return true;
112
144
  }
113
145
 
@@ -163,17 +195,30 @@ export default class ImageExpression implements Expression {
163
195
  }
164
196
 
165
197
  evaluate(ctx: EvaluationContext): null | ResolvedImage {
198
+ const primaryId = {
199
+ name: this.namePrimary.evaluate(ctx),
200
+ iconsetId: this.iconsetIdPrimary
201
+ };
202
+
203
+ const secondaryId = this.nameSecondary ? {
204
+ name: this.nameSecondary.evaluate(ctx),
205
+ iconsetId: this.iconsetIdSecondary
206
+ } : undefined;
207
+
166
208
  const value = ResolvedImage.build(
167
- this.inputPrimary.evaluate(ctx),
168
- this.inputSecondary ? this.inputSecondary.evaluate(ctx) : undefined,
169
- this.inputPrimaryParams ? this.evaluateParams(ctx, this.inputPrimaryParams) : undefined,
170
- this.inputSecondaryParams ? this.evaluateParams(ctx, this.inputSecondaryParams) : undefined
209
+ primaryId,
210
+ secondaryId,
211
+ this.paramsPrimary ? this.evaluateParams(ctx, this.paramsPrimary) : undefined,
212
+ this.paramsSecondary ? this.evaluateParams(ctx, this.paramsSecondary) : undefined
171
213
  );
214
+
172
215
  if (value && ctx.availableImages) {
173
- value.available = ctx.availableImages.indexOf(value.namePrimary) > -1;
174
- // If there's a secondary variant, only mark it available if both are present
175
- if (value.nameSecondary && value.available && ctx.availableImages) {
176
- value.available = ctx.availableImages.indexOf(value.nameSecondary) > -1;
216
+ const primaryId = value.getPrimary().id;
217
+ value.available = ctx.availableImages.some((id) => ImageId.isEqual(id, primaryId));
218
+ if (value.available) {
219
+ // If there's a secondary variant, only mark it available if both are present
220
+ const secondaryId = value.getSecondary() ? value.getSecondary().id : null;
221
+ if (secondaryId) value.available = ctx.availableImages.some((id) => ImageId.isEqual(id, secondaryId));
177
222
  }
178
223
  }
179
224
 
@@ -181,20 +226,22 @@ export default class ImageExpression implements Expression {
181
226
  }
182
227
 
183
228
  eachChild(fn: (_: Expression) => void) {
184
- fn(this.inputPrimary);
185
- if (this.inputPrimaryParams) {
186
- for (const key in this.inputPrimaryParams) {
187
- if (this.inputPrimaryParams[key]) {
188
- fn(this.inputPrimaryParams[key]);
229
+ fn(this.namePrimary);
230
+
231
+ if (this.paramsPrimary) {
232
+ for (const key in this.paramsPrimary) {
233
+ if (this.paramsPrimary[key]) {
234
+ fn(this.paramsPrimary[key]);
189
235
  }
190
236
  }
191
237
  }
192
- if (this.inputSecondary) {
193
- fn(this.inputSecondary);
194
- if (this.inputSecondaryParams) {
195
- for (const key in this.inputSecondaryParams) {
196
- if (this.inputSecondaryParams[key]) {
197
- fn(this.inputSecondaryParams[key]);
238
+
239
+ if (this.nameSecondary) {
240
+ fn(this.nameSecondary);
241
+ if (this.paramsSecondary) {
242
+ for (const key in this.paramsSecondary) {
243
+ if (this.paramsSecondary[key]) {
244
+ fn(this.paramsSecondary[key]);
198
245
  }
199
246
  }
200
247
  }
@@ -206,33 +253,39 @@ export default class ImageExpression implements Expression {
206
253
  return false;
207
254
  }
208
255
 
209
- serializeParams(params: Record<string, Expression> | undefined): {params: Record<string, SerializedExpression>} {
210
- const result: Record<string, SerializedExpression> = {};
256
+ serializeOptions(params: ImageParams, iconsetId: string): SerializedImageOptions | undefined {
257
+ const result: SerializedImageOptions = {};
258
+
259
+ if (iconsetId) {
260
+ result.iconset = {id: iconsetId};
261
+ }
262
+
211
263
  if (params) {
264
+ result.params = {};
212
265
  for (const key in params) {
213
266
  if (params[key]) {
214
- result[key] = params[key].serialize();
267
+ result.params[key] = params[key].serialize();
215
268
  }
216
269
  }
217
- } else {
218
- return undefined;
219
270
  }
220
271
 
221
- return {params: result};
272
+ return Object.keys(result).length > 0 ? result : undefined;
222
273
  }
223
274
 
224
275
  serialize(): SerializedExpression {
225
- const serialized: SerializedExpression = ["image", this.inputPrimary.serialize()];
276
+ const serialized: SerializedExpression = ['image', this.namePrimary.serialize()];
226
277
 
227
- if (this.inputPrimaryParams) {
228
- serialized.push(this.serializeParams(this.inputPrimaryParams));
278
+ if (this.paramsPrimary || this.iconsetIdPrimary) {
279
+ const options = this.serializeOptions(this.paramsPrimary, this.iconsetIdPrimary);
280
+ if (options) serialized.push(options);
229
281
  }
230
282
 
231
- if (this.inputSecondary) {
232
- serialized.push(this.inputSecondary.serialize());
283
+ if (this.nameSecondary) {
284
+ serialized.push(this.nameSecondary.serialize());
233
285
 
234
- if (this.inputSecondaryParams) {
235
- serialized.push(this.serializeParams(this.inputSecondaryParams));
286
+ if (this.paramsSecondary || this.iconsetIdSecondary) {
287
+ const options = this.serializeOptions(this.paramsSecondary, this.iconsetIdSecondary);
288
+ if (options) serialized.push(options);
236
289
  }
237
290
  }
238
291
 
@@ -19,6 +19,7 @@ import Literal from './literal';
19
19
  import Assertion from './assertion';
20
20
  import Coercion from './coercion';
21
21
  import At from './at';
22
+ import AtInterpolated from './at_interpolated';
22
23
  import In from './in';
23
24
  import IndexOf from './index_of';
24
25
  import Match from './match';
@@ -60,6 +61,7 @@ const expressions: ExpressionRegistry = {
60
61
  '<=': LessThanOrEqual,
61
62
  'array': Assertion,
62
63
  'at': At,
64
+ 'at-interpolated': AtInterpolated,
63
65
  'boolean': Assertion,
64
66
  'case': Case,
65
67
  'coalesce': Coalesce,
@@ -98,8 +100,7 @@ function rgba(ctx: EvaluationContext, [r, g, b, a]: Expression[]) {
98
100
  const alpha = a ? a.evaluate(ctx) : 1;
99
101
  const error = validateRGBA(r, g, b, alpha);
100
102
  if (error) throw new RuntimeError(error);
101
- // @ts-expect-error
102
- return new Color(r / 255 * alpha, g / 255 * alpha, b / 255 * alpha, alpha);
103
+ return new Color(r as unknown as number / 255 * alpha, g as unknown as number / 255 * alpha, b as unknown as number / 255 * alpha, alpha);
103
104
  }
104
105
 
105
106
  function hsla(ctx: EvaluationContext, [h, s, l, a]: Expression[]) {
@@ -1,10 +1,8 @@
1
1
  import UnitBezier from '@mapbox/unitbezier';
2
2
  import * as interpolate from '../../util/interpolate';
3
- import {toString, NumberType, ColorType, ValueType} from '../types';
3
+ import {toString, NumberType, ColorType} from '../types';
4
4
  import {findStopLessThanOrEqualTo} from '../stops';
5
5
  import {hcl, lab} from '../../util/color_spaces';
6
- import Literal from './literal';
7
- import RuntimeError from '../runtime_error';
8
6
 
9
7
  import type Color from '../../util/color';
10
8
  import type {Stops} from '../stops';
@@ -13,32 +11,30 @@ import type ParsingContext from '../parsing_context';
13
11
  import type EvaluationContext from '../evaluation_context';
14
12
  import type {Type} from '../types';
15
13
 
16
- export type InterpolationType = {
17
- name: 'linear';
18
- } | {
19
- name: 'exponential';
20
- base: number;
21
- } | {
22
- name: 'cubic-bezier';
23
- controlPoints: [number, number, number, number];
24
- };
14
+ export type InterpolationType =
15
+ | {name: 'linear'}
16
+ | {name: 'exponential'; base: number}
17
+ | {name: 'cubic-bezier'; controlPoints: [number, number, number, number]};
18
+
19
+ export type InterpolationOperator =
20
+ | 'interpolate'
21
+ | 'interpolate-hcl'
22
+ | 'interpolate-lab';
25
23
 
26
24
  class Interpolate implements Expression {
27
25
  type: Type;
28
26
 
29
- operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab';
27
+ operator: InterpolationOperator;
30
28
  interpolation: InterpolationType;
31
29
  input: Expression;
32
- dynamicStops: Expression | null;
33
30
  labels: Array<number>;
34
31
  outputs: Array<Expression>;
35
32
 
36
- constructor(type: Type, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, dynamicStops: Expression | null, stops: Stops) {
33
+ constructor(type: Type, operator: InterpolationOperator, interpolation: InterpolationType, input: Expression, stops: Stops) {
37
34
  this.type = type;
38
35
  this.operator = operator;
39
36
  this.interpolation = interpolation;
40
37
  this.input = input;
41
- this.dynamicStops = dynamicStops;
42
38
 
43
39
  this.labels = [];
44
40
  this.outputs = [];
@@ -67,11 +63,10 @@ class Interpolate implements Expression {
67
63
  return t;
68
64
  }
69
65
 
70
- static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Interpolate | null | undefined {
66
+ static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Interpolate | null | void {
71
67
  let [operator, interpolation, input, ...rest] = args;
72
68
 
73
69
  if (!Array.isArray(interpolation) || interpolation.length === 0) {
74
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
75
70
  return context.error(`Expected an interpolation type expression.`, 1);
76
71
  }
77
72
 
@@ -80,7 +75,6 @@ class Interpolate implements Expression {
80
75
  } else if (interpolation[0] === 'exponential') {
81
76
  const base = interpolation[1];
82
77
  if (typeof base !== 'number')
83
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
84
78
  return context.error(`Exponential interpolation requires a numeric base.`, 1, 1);
85
79
  interpolation = {
86
80
  name: 'exponential',
@@ -92,7 +86,6 @@ class Interpolate implements Expression {
92
86
  controlPoints.length !== 4 ||
93
87
  controlPoints.some(t => typeof t !== 'number' || t < 0 || t > 1)
94
88
  ) {
95
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
96
89
  return context.error('Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.', 1);
97
90
  }
98
91
 
@@ -101,17 +94,14 @@ class Interpolate implements Expression {
101
94
  controlPoints: (controlPoints as any)
102
95
  };
103
96
  } else {
104
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
105
97
  return context.error(`Unknown interpolation type ${String(interpolation[0])}`, 1, 0);
106
98
  }
107
99
 
108
- if (args.length - 1 < 3) {
109
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
110
- return context.error(`Expected at least 3 arguments, but found only ${args.length - 1}.`);
100
+ if (args.length - 1 < 4) {
101
+ return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
111
102
  }
112
103
 
113
104
  if (args.length - 1 > 3 && (args.length - 1) % 2 !== 0) {
114
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
115
105
  return context.error(`Expected an even number of arguments.`);
116
106
  }
117
107
 
@@ -127,15 +117,6 @@ class Interpolate implements Expression {
127
117
  outputType = context.expectedType;
128
118
  }
129
119
 
130
- // Exactly 3 arguments means that the steps are created by an expression
131
- if (args.length - 1 === 3) {
132
- const dynamicStops = context.parse(rest[0], 3, ValueType);
133
- if (!dynamicStops) return null;
134
-
135
- // @ts-expect-error - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'InterpolationType'.
136
- return new Interpolate(outputType, (operator as any), interpolation, input, dynamicStops, stops);
137
- }
138
-
139
120
  for (let i = 0; i < rest.length; i += 2) {
140
121
  const label = rest[i];
141
122
  const value = rest[i + 1];
@@ -144,12 +125,10 @@ class Interpolate implements Expression {
144
125
  const valueKey = i + 4;
145
126
 
146
127
  if (typeof label !== 'number') {
147
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
148
128
  return context.error('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey);
149
129
  }
150
130
 
151
131
  if (stops.length && stops[stops.length - 1][0] >= label) {
152
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
153
132
  return context.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.', labelKey);
154
133
  }
155
134
 
@@ -167,41 +146,15 @@ class Interpolate implements Expression {
167
146
  typeof outputType.N === 'number'
168
147
  )
169
148
  ) {
170
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Interpolate'.
171
149
  return context.error(`Type ${toString(outputType)} is not interpolatable.`);
172
150
  }
173
151
 
174
- // @ts-expect-error - TS2345 - Argument of type 'unknown' is not assignable to parameter of type 'InterpolationType'.
175
- return new Interpolate(outputType, (operator as any), interpolation, input, null, stops);
152
+ return new Interpolate(outputType, operator as InterpolationOperator, interpolation as InterpolationType, input as Expression, stops);
176
153
  }
177
154
 
178
155
  evaluate(ctx: EvaluationContext): Color {
179
- let labels = this.labels;
180
- let outputs = this.outputs;
181
-
182
- if (this.dynamicStops) {
183
- const dynamicStopsValue = (this.dynamicStops.evaluate(ctx) as [number]);
184
- if (dynamicStopsValue.length % 2 !== 0) {
185
- throw new RuntimeError('Expected an even number of arguments.');
186
- }
187
- labels = [];
188
- outputs = [];
189
- for (let i = 0; i < dynamicStopsValue.length; i += 2) {
190
- const label = dynamicStopsValue[i];
191
- const output = new Literal(NumberType, dynamicStopsValue[i + 1]);
192
- if (typeof label !== 'number') {
193
- throw new RuntimeError('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.');
194
- }
195
- if (labels.length && labels[labels.length - 1] >= label) {
196
- throw new RuntimeError('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.');
197
- }
198
- labels.push(label);
199
- outputs.push(output);
200
- }
201
- if (labels.length === 0) {
202
- throw new RuntimeError('Expected at least one input/output pair.');
203
- }
204
- }
156
+ const labels = this.labels;
157
+ const outputs = this.outputs;
205
158
 
206
159
  if (labels.length === 1) {
207
160
  return outputs[0].evaluate(ctx);
@@ -246,31 +199,26 @@ class Interpolate implements Expression {
246
199
  }
247
200
 
248
201
  serialize(): SerializedExpression {
249
- let interpolation;
202
+ let interpolation: [InterpolationType['name'], ...number[]];
250
203
  if (this.interpolation.name === 'linear') {
251
- interpolation = ["linear"];
204
+ interpolation = ['linear'];
252
205
  } else if (this.interpolation.name === 'exponential') {
253
206
  if (this.interpolation.base === 1) {
254
- interpolation = ["linear"];
207
+ interpolation = ['linear'];
255
208
  } else {
256
- interpolation = ["exponential", this.interpolation.base];
209
+ interpolation = ['exponential', this.interpolation.base];
257
210
  }
258
211
  } else {
259
- // @ts-expect-error - TS2769 - No overload matches this call.
260
- interpolation = ["cubic-bezier" ].concat(this.interpolation.controlPoints);
212
+ interpolation = ['cubic-bezier', ...this.interpolation.controlPoints];
261
213
  }
262
214
 
263
215
  const serialized = [this.operator, interpolation, this.input.serialize()];
264
216
 
265
- if (this.dynamicStops) {
266
- serialized.push(this.dynamicStops.serialize());
267
- } else {
268
- for (let i = 0; i < this.labels.length; i++) {
269
- serialized.push(
270
- this.labels[i],
271
- this.outputs[i].serialize()
272
- );
273
- }
217
+ for (let i = 0; i < this.labels.length; i++) {
218
+ serialized.push(
219
+ this.labels[i],
220
+ this.outputs[i].serialize()
221
+ );
274
222
  }
275
223
  return serialized;
276
224
  }
@@ -25,9 +25,8 @@ class Let implements Expression {
25
25
  fn(this.result);
26
26
  }
27
27
 
28
- static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Let | null | undefined {
28
+ static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Let | null | void {
29
29
  if (args.length < 4)
30
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Let'.
31
30
  return context.error(`Expected at least 3 arguments, but found ${args.length - 1} instead.`);
32
31
 
33
32
  const bindings: Array<[string, Expression]> = [];
@@ -35,12 +34,10 @@ class Let implements Expression {
35
34
  const name = args[i];
36
35
 
37
36
  if (typeof name !== 'string') {
38
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Let'.
39
37
  return context.error(`Expected string, but found ${typeof name} instead.`, i);
40
38
  }
41
39
 
42
40
  if (/[^a-zA-Z0-9_]/.test(name)) {
43
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Let'.
44
41
  return context.error(`Variable names must contain only alphanumeric characters or '_'.`, i);
45
42
  }
46
43
 
@@ -61,12 +58,10 @@ class Let implements Expression {
61
58
  }
62
59
 
63
60
  serialize(): SerializedExpression {
64
- const serialized = ["let"];
61
+ const serialized: SerializedExpression[] = ["let"];
65
62
  for (const [name, expr] of this.bindings) {
66
- // @ts-expect-error - TS2345 - Argument of type 'SerializedExpression' is not assignable to parameter of type 'string'.
67
63
  serialized.push(name, expr.serialize());
68
64
  }
69
- // @ts-expect-error - TS2345 - Argument of type 'SerializedExpression' is not assignable to parameter of type 'string'.
70
65
  serialized.push(this.result.serialize());
71
66
  return serialized;
72
67
  }
@@ -58,8 +58,7 @@ class Literal implements Expression {
58
58
  // Constant-folding can generate Literal expressions that you
59
59
  // couldn't actually generate with a "literal" expression,
60
60
  // so we have to implement an equivalent serialization here
61
- // @ts-expect-error - TS2769 - No overload matches this call.
62
- return ["rgba"].concat(this.value.toRenderColor(null).toArray());
61
+ return ["rgba" as SerializedExpression].concat(this.value.toRenderColor(null).toArray());
63
62
  } else if (this.value instanceof Formatted) {
64
63
  // Same as Color
65
64
  return this.value.serialize();
@@ -28,12 +28,10 @@ class Match implements Expression {
28
28
  this.otherwise = otherwise;
29
29
  }
30
30
 
31
- static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Match | null | undefined {
31
+ static parse(args: ReadonlyArray<unknown>, context: ParsingContext): Match | null | void {
32
32
  if (args.length < 5)
33
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
34
33
  return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
35
34
  if (args.length % 2 !== 1)
36
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
37
35
  return context.error(`Expected an even number of arguments.`);
38
36
 
39
37
  let inputType;
@@ -52,23 +50,17 @@ class Match implements Expression {
52
50
  }
53
51
 
54
52
  const labelContext = context.concat(i);
55
- // @ts-expect-error - TS2339 - Property 'length' does not exist on type 'unknown'.
56
- if (labels.length === 0) {
57
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
53
+ if ((labels as unknown[]).length === 0) {
58
54
  return labelContext.error('Expected at least one branch label.');
59
55
  }
60
56
 
61
- // @ts-expect-error - TS2488 - Type 'unknown' must have a '[Symbol.iterator]()' method that returns an iterator.
62
- for (const label of labels) {
57
+ for (const label of (labels as unknown[])) {
63
58
  if (typeof label !== 'number' && typeof label !== 'string') {
64
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
65
59
  return labelContext.error(`Branch labels must be numbers or strings.`);
66
60
  } else if (typeof label === 'number' && Math.abs(label) > Number.MAX_SAFE_INTEGER) {
67
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
68
61
  return labelContext.error(`Branch labels must be integers no larger than ${Number.MAX_SAFE_INTEGER}.`);
69
62
 
70
63
  } else if (typeof label === 'number' && Math.floor(label) !== label) {
71
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
72
64
  return labelContext.error(`Numeric branch labels must be integer values.`);
73
65
 
74
66
  } else if (!inputType) {
@@ -78,7 +70,6 @@ class Match implements Expression {
78
70
  }
79
71
 
80
72
  if (typeof cases[String(label)] !== 'undefined') {
81
- // @ts-expect-error - TS2322 - Type 'void' is not assignable to type 'Match'.
82
73
  return labelContext.error('Branch labels must be unique.');
83
74
  }
84
75