@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.
- package/dist/index.cjs +195 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +53 -15
- package/dist/index.es.js +195 -188
- package/dist/index.es.js.map +1 -1
- package/expression/definitions/index.ts +3 -2
- package/expression/expression_dependencies.ts +20 -23
- package/expression/index.ts +23 -30
- package/expression/is_constant.ts +20 -1
- package/expression/parsing_context.ts +73 -57
- package/expression/types.ts +14 -32
- package/feature_filter/index.ts +2 -1
- package/format.ts +10 -17
- package/function/index.ts +106 -155
- package/migrate/v8.ts +49 -80
- package/migrate/v9.ts +13 -13
- package/migrate.ts +2 -3
- package/package.json +1 -1
- package/reference/v8.json +61 -26
- package/types.ts +2 -0
- package/validate/validate_appearance.ts +1 -2
- package/validate/validate_expression.ts +12 -11
- package/validate_mapbox_api_supported.ts +1 -1
|
@@ -53,7 +53,8 @@ import type {Expression, ExpressionRegistry} from '../expression';
|
|
|
53
53
|
|
|
54
54
|
type LegacyFilterValue = string | number | boolean;
|
|
55
55
|
|
|
56
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
6
|
+
export type ExpressionDependencies = {
|
|
7
|
+
configDependencies: Set<string>;
|
|
8
|
+
isIndoorDependent: boolean;
|
|
9
|
+
};
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
20
|
-
if (e instanceof
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/expression/index.ts
CHANGED
|
@@ -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
|
-
|
|
89
|
-
this.
|
|
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
|
-
|
|
237
|
-
this.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
450
|
-
(new ZoomConstantExpression('
|
|
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
|
-
|
|
459
|
-
(new ZoomDependentExpression('
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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',
|
package/expression/types.ts
CHANGED
|
@@ -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
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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 {
|
package/feature_filter/index.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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(
|
|
49
|
+
return stringifyPretty(sorted, {indent: space});
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
export default format;
|