@mapbox/mapbox-gl-style-spec 14.31.0 → 14.32.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.
@@ -53,7 +53,8 @@ import type {Expression, ExpressionRegistry} from '../expression';
53
53
 
54
54
  type LegacyFilterValue = string | number | boolean;
55
55
 
56
- const expressions: ExpressionRegistry = {
56
+ // Prototype-less so that a lookup of an arbitrary operator name needs no own-property guard.
57
+ const expressions: ExpressionRegistry = Object.assign(Object.create(null) as ExpressionRegistry, {
57
58
  // special forms
58
59
  '==': Equals,
59
60
  '!=': NotEquals,
@@ -91,7 +92,7 @@ const expressions: ExpressionRegistry = {
91
92
  'within': Within,
92
93
  'distance': Distance,
93
94
  'config': Config
94
- };
95
+ });
95
96
 
96
97
  function rgba(ctx: EvaluationContext, [r, g, b, a]: Expression[]) {
97
98
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -3,31 +3,28 @@ import Config from "./definitions/config";
3
3
 
4
4
  import type {Expression} from "./expression";
5
5
 
6
- function getConfigDependencies(e: Expression): Set<string> {
7
- if (e instanceof Config) {
8
- const singleConfig = new Set([e.key]);
9
- return singleConfig;
10
- }
6
+ export type ExpressionDependencies = {
7
+ configDependencies: Set<string>;
8
+ isIndoorDependent: boolean;
9
+ };
11
10
 
12
- let result = new Set<string>();
13
- e.eachChild(arg => {
14
- result = new Set([...result, ...getConfigDependencies(arg)]);
15
- });
16
- return result;
17
- }
11
+ // Shared by every expression without config dependencies, which is the vast majority; read-only.
12
+ const NO_CONFIG_DEPENDENCIES: Set<string> = new Set();
18
13
 
19
- function isIndoorDependent(e: Expression): boolean {
20
- if (e instanceof CompoundExpression && e.name === 'is-active-floor') {
21
- return true;
14
+ function collect(e: Expression, deps: ExpressionDependencies) {
15
+ if (e instanceof Config) {
16
+ if (deps.configDependencies === NO_CONFIG_DEPENDENCIES) deps.configDependencies = new Set();
17
+ deps.configDependencies.add(e.key);
18
+ return; // `Config.eachChild` is a no-op
22
19
  }
23
-
24
- let result = false;
25
- e.eachChild(arg => {
26
- if (!result && isIndoorDependent(arg)) {
27
- result = true;
28
- }
29
- });
30
- return result;
20
+ if (e instanceof CompoundExpression && e.name === 'is-active-floor') deps.isIndoorDependent = true;
21
+ e.eachChild(arg => { collect(arg, deps); });
31
22
  }
32
23
 
33
- export {getConfigDependencies, isIndoorDependent};
24
+ // One traversal for both dependency kinds, and no per-node allocation: this runs for every
25
+ // expression of every property of every layer at style load, so the walk dominates what it finds.
26
+ export function getExpressionDependencies(e: Expression): ExpressionDependencies {
27
+ const deps = {configDependencies: NO_CONFIG_DEPENDENCIES, isIndoorDependent: false};
28
+ collect(e, deps);
29
+ return deps;
30
+ }
@@ -85,8 +85,9 @@ export class StyleExpression {
85
85
  this._evaluator = new EvaluationContext(scope, options, iconImageUseTheme);
86
86
  this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null;
87
87
  this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null;
88
- this.configDependencies = expressionDependencies.getConfigDependencies(expression);
89
- this.isIndoorDependent = expressionDependencies.isIndoorDependent(expression);
88
+ const dependencies = expressionDependencies.getExpressionDependencies(expression);
89
+ this.configDependencies = dependencies.configDependencies;
90
+ this.isIndoorDependent = dependencies.isIndoorDependent;
90
91
  }
91
92
 
92
93
  evaluateWithoutErrorHandling(
@@ -233,8 +234,9 @@ export class ZoomConstantExpression<Kind extends EvaluationKind> {
233
234
  this.isLightConstant = isLightConstant;
234
235
  this.isLineProgressConstant = isLineProgressConstant;
235
236
  this.isStateDependent = kind !== ('constant' as EvaluationKind) && !isConstant.isStateConstant(expression.expression);
236
- this.configDependencies = expressionDependencies.getConfigDependencies(expression.expression);
237
- this.isIndoorDependent = expressionDependencies.isIndoorDependent(expression.expression);
237
+ // Already walked by the wrapped `StyleExpression` over the same parsed expression
238
+ this.configDependencies = expression.configDependencies;
239
+ this.isIndoorDependent = expression.isIndoorDependent;
238
240
  }
239
241
 
240
242
  evaluateWithoutErrorHandling(
@@ -280,10 +282,11 @@ export class ZoomDependentExpression<Kind extends EvaluationKind> {
280
282
  this.zoomStops = zoomStops;
281
283
  this._styleExpression = expression;
282
284
  this.isStateDependent = kind !== ('camera' as EvaluationKind) && !isConstant.isStateConstant(expression.expression);
283
- this.isIndoorDependent = expressionDependencies.isIndoorDependent(expression.expression);
285
+ // Already walked by the wrapped `StyleExpression` over the same parsed expression
286
+ this.isIndoorDependent = expression.isIndoorDependent;
284
287
  this.isLightConstant = isLightConstant;
285
288
  this.isLineProgressConstant = isLineProgressConstant;
286
- this.configDependencies = expressionDependencies.getConfigDependencies(expression.expression);
289
+ this.configDependencies = expression.configDependencies;
287
290
  this.interpolationType = interpolationType;
288
291
  }
289
292
 
@@ -393,48 +396,42 @@ export interface CompositeExpression {
393
396
  export type StylePropertyExpression = ConstantExpression | SourceExpression | CameraExpression | CompositeExpression;
394
397
 
395
398
  export function createPropertyExpression(
396
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
397
- expression: any,
399
+ expression: unknown,
398
400
  propertySpec: StylePropertySpecification,
399
401
  scope?: string | null,
400
402
  options?: ConfigOptions | null,
401
403
  iconImageUseTheme?: string | null
402
404
  ): Result<StylePropertyExpression, Array<ParsingError>> {
403
- expression = createExpression(expression, propertySpec, scope, options, iconImageUseTheme);
404
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
405
- if (expression.result === 'error') {
406
- return expression as Result<StylePropertyExpression, Array<ParsingError>>;
405
+ const styleExpression = createExpression(expression, propertySpec, scope, options, iconImageUseTheme);
406
+ if (styleExpression.result === 'error') {
407
+ return styleExpression;
407
408
  }
408
409
 
409
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
410
- const parsed = expression.value.expression;
410
+ const parsed = styleExpression.value.expression;
411
411
 
412
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
413
412
  const isFeatureConstant = isConstant.isFeatureConstant(parsed);
414
413
  if (!isFeatureConstant && !supportsPropertyExpression(propertySpec)) {
415
414
  return error([new ParsingError('', 'data expressions not supported')]);
416
415
  }
417
416
 
418
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
419
- const isZoomConstant = isConstant.isGlobalPropertyConstant(parsed, ['zoom', 'pitch', 'distance-from-center']);
417
+ const globalDependencies = isConstant.getGlobalDependencies(parsed);
418
+
419
+ const isZoomConstant = (globalDependencies & isConstant.GlobalDependency.Zoom) === 0;
420
420
  if (!isZoomConstant && !supportsZoomExpression(propertySpec)) {
421
421
  return error([new ParsingError('', 'zoom expressions not supported')]);
422
422
  }
423
423
 
424
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
425
- const isLightConstant = isConstant.isGlobalPropertyConstant(parsed, ['measure-light']);
424
+ const isLightConstant = (globalDependencies & isConstant.GlobalDependency.Light) === 0;
426
425
  if (!isLightConstant && !supportsLightExpression(propertySpec)) {
427
426
  return error([new ParsingError('', 'measure-light expression not supported')]);
428
427
  }
429
428
 
430
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
431
- const isLineProgressConstant = isConstant.isGlobalPropertyConstant(parsed, ['line-progress']);
429
+ const isLineProgressConstant = (globalDependencies & isConstant.GlobalDependency.LineProgress) === 0;
432
430
  if (!isLineProgressConstant && !supportsLineProgressExpression(propertySpec)) {
433
431
  return error([new ParsingError('', 'line-progress expression not supported')]);
434
432
  }
435
433
 
436
434
  const canRelaxZoomRestriction = propertySpec.expression && propertySpec.expression.relaxZoomRestriction;
437
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
438
435
  const zoomCurve = findZoomCurve(parsed);
439
436
  if (!zoomCurve && !isZoomConstant && !canRelaxZoomRestriction) {
440
437
  return error([new ParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression, or in the properties of atmosphere.')]);
@@ -446,19 +443,15 @@ export function createPropertyExpression(
446
443
 
447
444
  if (!zoomCurve) {
448
445
  return success((isFeatureConstant && isLineProgressConstant) ?
449
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
450
- (new ZoomConstantExpression('constant', expression.value, isLightConstant, isLineProgressConstant) as ConstantExpression) :
451
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
452
- (new ZoomConstantExpression('source', expression.value, isLightConstant, isLineProgressConstant) as SourceExpression));
446
+ (new ZoomConstantExpression('constant', styleExpression.value, isLightConstant, isLineProgressConstant) as ConstantExpression) :
447
+ (new ZoomConstantExpression('source', styleExpression.value, isLightConstant, isLineProgressConstant) as SourceExpression));
453
448
  }
454
449
 
455
450
  const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined;
456
451
 
457
452
  return success((isFeatureConstant && isLineProgressConstant) ?
458
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
459
- (new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType, isLightConstant, isLineProgressConstant) as CameraExpression) :
460
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
461
- (new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType, isLightConstant, isLineProgressConstant) as CompositeExpression));
453
+ (new ZoomDependentExpression('camera', styleExpression.value, zoomCurve.labels, interpolationType, isLightConstant, isLineProgressConstant) as CameraExpression) :
454
+ (new ZoomDependentExpression('composite', styleExpression.value, zoomCurve.labels, interpolationType, isLightConstant, isLineProgressConstant) as CompositeExpression));
462
455
  }
463
456
 
464
457
  // serialization wrapper for old-style stop functions normalized to the
@@ -69,4 +69,23 @@ function isGlobalPropertyConstant(e: Expression, properties: Array<string>): boo
69
69
  return isGlobalPropertyConstantSet(e, new Set(properties));
70
70
  }
71
71
 
72
- export {isFeatureConstant, isGlobalPropertyConstant, isGlobalPropertyConstantSet, isStateConstant};
72
+ // Bitmask so that one walk answers all three: `createPropertyExpression` used to call
73
+ // `isGlobalPropertyConstant` once per group, traversing every expression of every property in
74
+ // full each time, since those calls only exit early on the rare dependent branch.
75
+ export const GlobalDependency = {Zoom: 1, Light: 2, LineProgress: 4};
76
+
77
+ const globalDependencyFlags: Record<string, number> = {
78
+ 'zoom': GlobalDependency.Zoom,
79
+ 'pitch': GlobalDependency.Zoom,
80
+ 'distance-from-center': GlobalDependency.Zoom,
81
+ 'measure-light': GlobalDependency.Light,
82
+ 'line-progress': GlobalDependency.LineProgress
83
+ };
84
+
85
+ function getGlobalDependencies(e: Expression): number {
86
+ let flags = e instanceof CompoundExpression ? globalDependencyFlags[e.name] || 0 : 0;
87
+ e.eachChild(arg => { flags |= getGlobalDependencies(arg); });
88
+ return flags;
89
+ }
90
+
91
+ export {isFeatureConstant, isGlobalPropertyConstant, isGlobalPropertyConstantSet, isStateConstant, getGlobalDependencies};
@@ -1,5 +1,5 @@
1
1
  import Scope from './scope';
2
- import {checkSubtype} from './types';
2
+ import {checkSubtype, NullType, StringType, BooleanType, NumberType} from './types';
3
3
  import ParsingError from './parsing_error';
4
4
  import Literal from './definitions/literal';
5
5
  import Assertion from './definitions/assertion';
@@ -15,6 +15,7 @@ import Var from './definitions/var';
15
15
 
16
16
  import type {Expression, ExpressionRegistry} from './expression';
17
17
  import type {Type} from './types';
18
+ import type {Value} from './values';
18
19
  import type {ConfigOptions} from '../types/config_options';
19
20
 
20
21
  /**
@@ -77,9 +78,9 @@ class ParsingContext {
77
78
  index?: number,
78
79
  expectedType?: Type | null,
79
80
  bindings?: Array<[string, Expression]>,
80
- options: {
81
+ options?: {
81
82
  typeAnnotation?: 'assert' | 'coerce' | 'omit';
82
- } = {},
83
+ },
83
84
  ): Expression | null | void {
84
85
  if (index || expectedType) {
85
86
  const prevExpectedType = this.expectedType;
@@ -111,9 +112,9 @@ class ParsingContext {
111
112
  key: string,
112
113
  expectedType?: Type | null,
113
114
  bindings?: Array<[string, Expression]>,
114
- options: {
115
+ options?: {
115
116
  typeAnnotation?: 'assert' | 'coerce' | 'omit';
116
- } = {},
117
+ },
117
118
  ): Expression | null | void {
118
119
  const prevExpectedType = this.expectedType;
119
120
  const prevScope = this.scope;
@@ -129,67 +130,60 @@ class ParsingContext {
129
130
  return result;
130
131
  }
131
132
 
133
+ // Applies `expectedType` to a freshly parsed expression, wrapping it in an inferred assertion
134
+ // or coercion where that is what the expected type calls for. Returns null if the type is wrong.
135
+ _annotateToExpectedType(parsed: Expression, options?: {typeAnnotation?: 'assert' | 'coerce' | 'omit'}): Expression | null {
136
+ const expected = this.expectedType;
137
+ if (!expected) return parsed;
138
+ const actual = parsed.type;
139
+
140
+ // When we expect a number, string, boolean, or array but have a value, wrap it in an assertion.
141
+ // When we expect a color or formatted string, but have a string or value, wrap it in a coercion.
142
+ // Otherwise, we do static type-checking.
143
+ //
144
+ // These behaviors are overridable for:
145
+ // * The "coalesce" operator, which needs to omit type annotations.
146
+ // * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion.
147
+ //
148
+ if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') {
149
+ return annotate(parsed, expected, options?.typeAnnotation || 'assert');
150
+ } else if ((expected.kind === 'color' || expected.kind === 'formatted' || expected.kind === 'resolvedImage') && (actual.kind === 'value' || actual.kind === 'string')) {
151
+ return annotate(parsed, expected, options?.typeAnnotation || 'coerce');
152
+ } else if (this.checkSubtype(expected, actual)) {
153
+ return null;
154
+ }
155
+
156
+ return parsed;
157
+ }
158
+
132
159
  _parse(
133
160
  expr: unknown,
134
- options: {
161
+ options?: {
135
162
  typeAnnotation?: 'assert' | 'coerce' | 'omit';
136
163
  },
137
164
  ): Expression | null | void {
138
- if (expr === null || typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') {
139
- expr = ['literal', expr];
140
- }
165
+ // Scalars are nearly half of all parsed expressions and are always literal, so build the
166
+ // `Literal` here rather than round-tripping a `['literal', v]` array through the registry.
167
+ const scalarType = expr === null ? NullType :
168
+ typeof expr === 'string' ? StringType :
169
+ typeof expr === 'boolean' ? BooleanType :
170
+ typeof expr === 'number' ? NumberType : null;
141
171
 
142
- if (Array.isArray(expr)) {
172
+ let result: Expression | null | void;
173
+
174
+ if (scalarType) {
175
+ result = new Literal(scalarType, expr as Value);
176
+ } else if (Array.isArray(expr)) {
143
177
  if (expr.length === 0) {
144
178
  return this.error(`Expected an array with at least one element. If you wanted a literal array, use ["literal", []].`);
145
179
  }
146
180
 
147
- const Expr = typeof expr[0] === 'string' && Object.hasOwn(this.registry, expr[0]) ? this.registry[expr[0]] : undefined;
148
- if (Expr) {
149
- let parsed = Expr.parse(expr, this);
150
- if (!parsed) return null;
151
-
152
- if (this.expectedType) {
153
- const expected = this.expectedType;
154
- const actual = parsed.type;
155
-
156
- // When we expect a number, string, boolean, or array but have a value, wrap it in an assertion.
157
- // When we expect a color or formatted string, but have a string or value, wrap it in a coercion.
158
- // Otherwise, we do static type-checking.
159
- //
160
- // These behaviors are overridable for:
161
- // * The "coalesce" operator, which needs to omit type annotations.
162
- // * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion.
163
- //
164
- if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') {
165
- parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
166
- } else if ((expected.kind === 'color' || expected.kind === 'formatted' || expected.kind === 'resolvedImage') && (actual.kind === 'value' || actual.kind === 'string')) {
167
- parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
168
- } else if (this.checkSubtype(expected, actual)) {
169
- return null;
170
- }
171
- }
172
-
173
- // If an expression's arguments are all literals, we can evaluate
174
- // it immediately and replace it with a literal value in the
175
- // parsed/compiled result. Expressions that expect an image should
176
- // not be resolved here so we can later get the available images.
177
- if (!(parsed instanceof Literal) && (parsed.type.kind !== 'resolvedImage') && isConstant(parsed)) {
178
- const ec = new EvaluationContext(this._scope, this.options, this.iconImageUseTheme);
179
- try {
180
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
181
- parsed = new Literal(parsed.type, parsed.evaluate(ec));
182
- } catch (e) {
183
- this.error((e as Error).message);
184
- return null;
185
- }
186
- }
187
-
188
- return parsed;
189
- }
190
-
181
+ const Expr = this.registry[expr[0] as string];
191
182
  // Try to parse as array
192
- return Coercion.parse(['to-array', expr], this);
183
+ if (!Expr) return Coercion.parse(['to-array', expr], this);
184
+
185
+ result = Expr.parse(expr, this);
186
+ if (!result) return null;
193
187
  } else if (typeof expr === 'undefined') {
194
188
  return this.error(`'undefined' value invalid. Use null instead.`);
195
189
  } else if (typeof expr === 'object') {
@@ -197,6 +191,26 @@ class ParsingContext {
197
191
  } else {
198
192
  return this.error(`Expected an array, but found ${typeof expr} instead.`);
199
193
  }
194
+
195
+ let parsed = this._annotateToExpectedType(result, options);
196
+ if (!parsed) return null;
197
+
198
+ // If an expression's arguments are all literals, we can evaluate
199
+ // it immediately and replace it with a literal value in the
200
+ // parsed/compiled result. Expressions that expect an image should
201
+ // not be resolved here so we can later get the available images.
202
+ if (!(parsed instanceof Literal) && (parsed.type.kind !== 'resolvedImage') && isConstant(parsed)) {
203
+ const ec = new EvaluationContext(this._scope, this.options, this.iconImageUseTheme);
204
+ try {
205
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
206
+ parsed = new Literal(parsed.type, parsed.evaluate(ec));
207
+ } catch (e) {
208
+ this.error((e as Error).message);
209
+ return null;
210
+ }
211
+ }
212
+
213
+ return parsed;
200
214
  }
201
215
 
202
216
  /**
@@ -235,10 +249,12 @@ class ParsingContext {
235
249
  * overload signatures without polluting the parent errors list.
236
250
  * @private
237
251
  */
252
+ // `path` is shared rather than copied: argument parsing pushes and pops it in balance, and
253
+ // `error` snapshots `key` into the message eagerly, so the fork never observes a stale path.
238
254
  _forkForSignature(): ParsingContext {
239
255
  return new ParsingContext(
240
256
  this.registry,
241
- this.path.slice(),
257
+ this.path,
242
258
  null,
243
259
  this.scope,
244
260
  [],
@@ -273,7 +289,7 @@ class ParsingContext {
273
289
 
274
290
  export default ParsingContext;
275
291
 
276
- const CONSTANT_FOLD_EXCLUDED_GLOBALS = new Set([
292
+ const CONSTANT_FOLD_EXCLUDED_GLOBALS: ReadonlySet<string> = new Set([
277
293
  'zoom', 'heatmap-density', 'worldview', 'line-progress', 'raster-value',
278
294
  'sky-radial-progress', 'accumulated', 'is-supported-script', 'pitch',
279
295
  'distance-from-center', 'measure-light', 'raster-particle-speed', 'is-active-floor',
@@ -87,44 +87,26 @@ export function toString(type: Type): string {
87
87
  }
88
88
  }
89
89
 
90
- const valueMemberTypes = [
91
- NullType,
92
- NumberType,
93
- StringType,
94
- BooleanType,
95
- ColorType,
96
- FormattedType,
97
- ObjectType,
98
- array(ValueType),
99
- ResolvedImageType
100
- ];
101
-
102
90
  /**
103
91
  * Returns null if `t` is a subtype of `expected`; otherwise returns an
104
92
  * error message.
105
93
  * @private
106
94
  */
107
- export function checkSubtype(expected: Type, t: Type): string | null | undefined {
108
- if (t.kind === 'error') {
109
- // Error is a subtype of every type
110
- return null;
111
- } else if (expected.kind === 'array') {
112
- if (t.kind === 'array' &&
113
- ((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) &&
114
- (typeof expected.N !== 'number' || expected.N === t.N)) {
115
- return null;
116
- }
117
- } else if (expected.kind === t.kind) {
118
- return null;
119
- } else if (expected.kind === 'value') {
120
- for (const memberType of valueMemberTypes) {
121
- if (!checkSubtype(memberType, t)) {
122
- return null;
123
- }
124
- }
125
- }
95
+ export function checkSubtype(expected: Type, t: Type): string | null {
96
+ return isSubtype(expected, t) ? null : `Expected ${toString(expected)} but found ${toString(t)} instead.`;
97
+ }
126
98
 
127
- return `Expected ${toString(expected)} but found ${toString(t)} instead.`;
99
+ // Boolean, so that checks made speculatively rather than to report an error - overload resolution
100
+ // in `CompoundExpression.parse`, the recursion below - build no message that nobody will read.
101
+ function isSubtype(expected: Type, t: Type): boolean {
102
+ if (t.kind === 'error') return true; // error is a subtype of every type
103
+ if (expected.kind === 'array') {
104
+ return t.kind === 'array' &&
105
+ ((t.N === 0 && t.itemType.kind === 'value') || isSubtype(expected.itemType, t.itemType)) &&
106
+ (typeof expected.N !== 'number' || expected.N === t.N);
107
+ }
108
+ // `value` is a supertype of every type except `collator`
109
+ return expected.kind === t.kind || (expected.kind === 'value' && t.kind !== 'collator');
128
110
  }
129
111
 
130
112
  export function isValidType(provided: Type, allowedTypes: Array<Type>): boolean {
@@ -203,6 +203,7 @@ function collapseDynamicBooleanExpressions(expression: any): any {
203
203
  * @param {Array<any>} filter the filter expression mutated in-place.
204
204
  */
205
205
  function unionDynamicBranches(filter: unknown[]) {
206
+ if (!Array.isArray(filter)) return;
206
207
  let isBranchingDynamically = false;
207
208
  const branches: unknown[] = [];
208
209
  if (filter[0] === 'case') {
@@ -262,7 +263,7 @@ function isRootExpressionDynamic(expression: string): boolean {
262
263
  expression === 'distance-from-center';
263
264
  }
264
265
 
265
- const dynamicConditionExpressions = new Set([
266
+ const dynamicConditionExpressions: ReadonlySet<string> = new Set([
266
267
  'in',
267
268
  '==',
268
269
  '!=',
package/format.ts CHANGED
@@ -1,26 +1,21 @@
1
- /* eslint-disable @typescript-eslint/ban-ts-comment */
2
- // @ts-nocheck
3
-
4
1
  import reference from './reference/latest';
5
2
  import stringifyPretty from 'json-stringify-pretty-compact';
6
3
 
7
- function sortKeysBy(obj, reference) {
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- const result: Record<string, any> = {};
4
+ type ObjectLike = Record<string, unknown>;
5
+
6
+ function sortKeysBy<T extends ObjectLike>(obj: T, reference: ObjectLike): T {
7
+ const result: ObjectLike = {};
10
8
  for (const key in reference) {
11
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
12
9
  if (obj[key] !== undefined) {
13
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
14
10
  result[key] = obj[key];
15
11
  }
16
12
  }
17
13
  for (const key in obj) {
18
14
  if (result[key] === undefined) {
19
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
20
15
  result[key] = obj[key];
21
16
  }
22
17
  }
23
- return result;
18
+ return result as T;
24
19
  }
25
20
 
26
21
  /**
@@ -44,16 +39,14 @@ function sortKeysBy(obj, reference) {
44
39
  * fs.writeFileSync('./dest.json', format(style));
45
40
  * fs.writeFileSync('./dest.min.json', format(style, 0));
46
41
  */
47
- function format(style, space = 2) {
48
- style = sortKeysBy(style, reference.$root);
42
+ function format(style: unknown, space = 2): string {
43
+ const sorted = sortKeysBy(style as ObjectLike, reference.$root as ObjectLike) as {layers?: ObjectLike[]};
49
44
 
50
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
51
- if (style.layers) {
52
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
53
- style.layers = style.layers.map((layer) => sortKeysBy(layer, reference.layer));
45
+ if (sorted.layers) {
46
+ sorted.layers = sorted.layers.map((layer) => sortKeysBy(layer, reference.layer as ObjectLike));
54
47
  }
55
48
 
56
- return stringifyPretty(style, {indent: space});
49
+ return stringifyPretty(sorted, {indent: space});
57
50
  }
58
51
 
59
52
  export default format;