@mapbox/mapbox-gl-style-spec 14.28.0 → 14.29.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/composite.ts +5 -5
- package/diff.ts +38 -40
- package/dist/index.cjs +362 -238
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +101 -13
- package/dist/index.es.js +362 -238
- package/dist/index.es.js.map +1 -1
- package/expression/compound_expression.ts +6 -6
- package/expression/definitions/assertion.ts +9 -12
- package/expression/definitions/at.ts +1 -1
- package/expression/definitions/at_interpolated.ts +1 -1
- package/expression/definitions/case.ts +1 -2
- package/expression/definitions/coalesce.ts +1 -1
- package/expression/definitions/coercion.ts +10 -12
- package/expression/definitions/collator.ts +8 -8
- package/expression/definitions/comparison.ts +0 -1
- package/expression/definitions/distance.ts +135 -73
- package/expression/definitions/format.ts +10 -13
- package/expression/definitions/image.ts +8 -8
- package/expression/definitions/index.ts +92 -90
- package/expression/definitions/interpolate.ts +18 -19
- package/expression/definitions/let.ts +1 -2
- package/expression/definitions/literal.ts +1 -1
- package/expression/definitions/match.ts +7 -8
- package/expression/definitions/number_format.ts +7 -8
- package/expression/definitions/step.ts +11 -11
- package/expression/definitions/within.ts +23 -24
- package/expression/evaluation_context.ts +4 -4
- package/expression/expression.ts +1 -1
- package/expression/index.ts +43 -21
- package/expression/parsing_context.ts +2 -2
- package/expression/stops.ts +2 -2
- package/expression/values.ts +2 -2
- package/feature_filter/index.ts +3 -3
- package/function/convert.ts +8 -8
- package/function/index.ts +4 -2
- package/group_by_layout.ts +6 -4
- package/package.json +1 -1
- package/read_style.ts +2 -2
- package/reference/v8.json +86 -5
- package/types/config_options.ts +1 -1
- package/types.ts +16 -6
- package/util/properties.ts +1 -1
- package/validate/validate.ts +15 -5
- package/validate/validate_appearance.ts +7 -7
- package/validate/validate_array.ts +7 -7
- package/validate/validate_enum.ts +2 -3
- package/validate/validate_fog.ts +2 -2
- package/validate/validate_function.ts +14 -13
- package/validate/validate_layer.ts +10 -9
- package/validate/validate_light.ts +2 -2
- package/validate/validate_lights.ts +3 -3
- package/validate/validate_number.ts +10 -7
- package/validate/validate_object.ts +11 -10
- package/validate/validate_option.ts +24 -9
- package/validate/validate_property.ts +4 -4
- package/validate/validate_rain.ts +1 -1
- package/validate/validate_snow.ts +1 -1
- package/validate/validate_style.ts +2 -1
- package/validate/validate_terrain.ts +2 -2
- package/validate_mapbox_api_supported.ts +5 -6
- package/validate_style.min.ts +2 -1
- package/visit.ts +11 -10
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
import type {StyleReference} from '../reference/latest';
|
|
16
16
|
import type {StyleSpecification} from '../types';
|
|
17
17
|
import type {StylePropertySpecification} from '../style-spec';
|
|
18
|
+
import type {ArraySpec} from './validate_array';
|
|
18
19
|
|
|
19
|
-
function hasObjectStops(value:
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
20
|
+
function hasObjectStops(value: Record<PropertyKey, unknown>): value is {stops: Array<Record<PropertyKey, unknown>>} {
|
|
21
21
|
const stops = value['stops'];
|
|
22
22
|
return Array.isArray(stops) && Array.isArray(stops[0]) && isObject(stops[0][0]);
|
|
23
23
|
}
|
|
@@ -25,11 +25,13 @@ function hasObjectStops(value: object): value is {stops: Array<Record<PropertyKe
|
|
|
25
25
|
export type FunctionValidatorOptions = {
|
|
26
26
|
key: string;
|
|
27
27
|
value: unknown;
|
|
28
|
-
valueSpec
|
|
28
|
+
valueSpec?: unknown;
|
|
29
29
|
style: Partial<StyleSpecification>;
|
|
30
30
|
styleSpec: StyleReference;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
type FunctionValidator = (options: FunctionValidatorOptions) => ValidationError[];
|
|
34
|
+
|
|
33
35
|
export default function validateFunction(options: FunctionValidatorOptions): ValidationError[] {
|
|
34
36
|
const key = options.key;
|
|
35
37
|
const value = options.value;
|
|
@@ -38,7 +40,7 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
38
40
|
return [new ValidationError(key, value, `object expected, ${getType(value)} found`)];
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
const functionValueSpec = options.valueSpec;
|
|
43
|
+
const functionValueSpec = options.valueSpec as StylePropertySpecification;
|
|
42
44
|
const functionType = unbundle(value.type);
|
|
43
45
|
let stopKeyType: string | undefined;
|
|
44
46
|
let stopDomainValues: Partial<Record<string | number, boolean>> = {};
|
|
@@ -70,14 +72,14 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
70
72
|
errors.push(new ValidationError(options.key, options.value, 'missing required property "stops"'));
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
if (functionType === 'exponential' &&
|
|
75
|
+
if (functionType === 'exponential' && functionValueSpec.expression && !supportsInterpolation(functionValueSpec)) {
|
|
74
76
|
errors.push(new ValidationError(options.key, options.value, 'exponential functions not supported'));
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
if (options.styleSpec.$version >= 8) {
|
|
78
|
-
if (isPropertyFunction && !supportsPropertyExpression(functionValueSpec
|
|
80
|
+
if (isPropertyFunction && !supportsPropertyExpression(functionValueSpec)) {
|
|
79
81
|
errors.push(new ValidationError(options.key, options.value, 'property functions not supported'));
|
|
80
|
-
} else if (isZoomFunction && !supportsZoomExpression(functionValueSpec
|
|
82
|
+
} else if (isZoomFunction && !supportsZoomExpression(functionValueSpec)) {
|
|
81
83
|
errors.push(new ValidationError(options.key, options.value, 'zoom functions not supported'));
|
|
82
84
|
}
|
|
83
85
|
}
|
|
@@ -99,10 +101,10 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
99
101
|
errors = errors.concat(validateArray({
|
|
100
102
|
key: options.key,
|
|
101
103
|
value,
|
|
102
|
-
valueSpec: options.valueSpec,
|
|
104
|
+
valueSpec: options.valueSpec as ArraySpec,
|
|
103
105
|
style: options.style,
|
|
104
106
|
styleSpec: options.styleSpec,
|
|
105
|
-
arrayElementValidator: validateFunctionStop
|
|
107
|
+
arrayElementValidator: validateFunctionStop as (...args: unknown[]) => ValidationError[]
|
|
106
108
|
}));
|
|
107
109
|
|
|
108
110
|
if (Array.isArray(value) && value.length === 0) {
|
|
@@ -157,7 +159,7 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
157
159
|
valueSpec: {zoom: {}},
|
|
158
160
|
style: options.style,
|
|
159
161
|
styleSpec: options.styleSpec,
|
|
160
|
-
objectElementValidators: {zoom: validateNumber, value: validateStopDomainValue}
|
|
162
|
+
objectElementValidators: {zoom: validateNumber as FunctionValidator, value: validateStopDomainValue}
|
|
161
163
|
}));
|
|
162
164
|
} else {
|
|
163
165
|
errors = errors.concat(validateStopDomainValue({
|
|
@@ -182,7 +184,7 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
182
184
|
}));
|
|
183
185
|
}
|
|
184
186
|
|
|
185
|
-
function validateStopDomainValue(options: FunctionValidatorOptions, stop?:
|
|
187
|
+
function validateStopDomainValue(options: FunctionValidatorOptions, stop?: object): ValidationError[] {
|
|
186
188
|
const type = getType(options.value);
|
|
187
189
|
const value = unbundle(options.value);
|
|
188
190
|
|
|
@@ -200,14 +202,13 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
200
202
|
|
|
201
203
|
if (type !== 'number' && functionType !== 'categorical') {
|
|
202
204
|
let message = `number expected, ${type} found`;
|
|
203
|
-
if (supportsPropertyExpression(functionValueSpec
|
|
205
|
+
if (supportsPropertyExpression(functionValueSpec) && functionType === undefined) {
|
|
204
206
|
message += '\nIf you intended to use a categorical function, specify `"type": "categorical"`.';
|
|
205
207
|
}
|
|
206
208
|
return [new ValidationError(options.key, reportValue, message)];
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
if (functionType === 'categorical' && type === 'number' && (typeof value !== 'number' || !isFinite(value) || Math.floor(value) !== value)) {
|
|
210
|
-
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to number|string|boolean by the check above
|
|
211
212
|
return [new ValidationError(options.key, reportValue, `integer expected, found ${String(value)}`)];
|
|
212
213
|
}
|
|
213
214
|
|
|
@@ -11,6 +11,7 @@ import {isObject, isString} from '../util/get_type';
|
|
|
11
11
|
|
|
12
12
|
import type {StyleReference} from '../reference/latest';
|
|
13
13
|
import type {PropertyValidatorOptions} from './validate_property';
|
|
14
|
+
import type {ArraySpec} from './validate_array';
|
|
14
15
|
import type {StyleSpecification, LayerSpecification, GeoJSONSourceSpecification} from '../types';
|
|
15
16
|
|
|
16
17
|
type LayerValidatorOptions = {
|
|
@@ -43,7 +44,7 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
43
44
|
if (layer.id) {
|
|
44
45
|
const layerId = unbundle(layer.id) as string;
|
|
45
46
|
for (let i = 0; i < options.arrayIndex; i++) {
|
|
46
|
-
const otherLayer = style.layers[i]
|
|
47
|
+
const otherLayer = style.layers![i]!;
|
|
47
48
|
if (unbundle(otherLayer.id) === layerId) {
|
|
48
49
|
errors.push(new ValidationError(key, layer.id, `duplicate layer id "${layerId}", previously used at line ${(otherLayer.id as {__line__?: number}).__line__}`));
|
|
49
50
|
}
|
|
@@ -59,7 +60,7 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
59
60
|
|
|
60
61
|
let parent: LayerSpecification | undefined;
|
|
61
62
|
|
|
62
|
-
style.layers
|
|
63
|
+
style.layers!.forEach((layer) => {
|
|
63
64
|
if (unbundle(layer.id) === ref) parent = layer;
|
|
64
65
|
});
|
|
65
66
|
|
|
@@ -94,10 +95,10 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
94
95
|
errors.push(new ValidationError(key, layer.source, 'raster-dem source can only be used with layer type \'hillshade\'.'));
|
|
95
96
|
} else if (sourceType === 'raster-array' && !['raster', 'raster-particle'].includes(type)) {
|
|
96
97
|
errors.push(new ValidationError(key, layer.source, `raster-array source can only be used with layer type \'raster\'.`));
|
|
97
|
-
} else if (type === 'line' && layer.paint && layer.paint['line-gradient'] &&
|
|
98
|
+
} else if (type === 'line' && layer.paint && (layer.paint as Record<string, unknown>)['line-gradient'] &&
|
|
98
99
|
(sourceType === 'geojson' && !(source as GeoJSONSourceSpecification).lineMetrics)) {
|
|
99
100
|
errors.push(new ValidationError(key, layer, `layer "${layer.id as string}" specifies a line-gradient, which requires the GeoJSON source to have \`lineMetrics\` enabled.`));
|
|
100
|
-
} else if (type === 'line' && layer.paint && layer.paint['line-trim-offset'] &&
|
|
101
|
+
} else if (type === 'line' && layer.paint && (layer.paint as Record<string, unknown>)['line-trim-offset'] &&
|
|
101
102
|
(sourceType === 'geojson' && !(source as GeoJSONSourceSpecification).lineMetrics)) {
|
|
102
103
|
errors.push(new ValidationError(key, layer, `layer "${layer.id as string}" specifies a line-trim-offset, which requires the GeoJSON source to have \`lineMetrics\` enabled.`));
|
|
103
104
|
} else if (type === 'raster-particle' && sourceType !== 'raster-array') {
|
|
@@ -143,8 +144,8 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
143
144
|
style: options.style,
|
|
144
145
|
styleSpec: options.styleSpec,
|
|
145
146
|
objectElementValidators: {
|
|
146
|
-
'*'(options
|
|
147
|
-
return validateLayoutProperty({layerType: type, ...options});
|
|
147
|
+
'*'(options) {
|
|
148
|
+
return validateLayoutProperty({layerType: type, ...options} as PropertyValidatorOptions);
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
151
|
});
|
|
@@ -158,8 +159,8 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
158
159
|
style: options.style,
|
|
159
160
|
styleSpec: options.styleSpec,
|
|
160
161
|
objectElementValidators: {
|
|
161
|
-
'*'(options
|
|
162
|
-
return validatePaintProperty({layerType: type, layer, ...options});
|
|
162
|
+
'*'(options) {
|
|
163
|
+
return validatePaintProperty({layerType: type, layer, ...options} as PropertyValidatorOptions);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
});
|
|
@@ -169,7 +170,7 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
169
170
|
key: options.key,
|
|
170
171
|
value: options.value,
|
|
171
172
|
|
|
172
|
-
valueSpec: options.valueSpec,
|
|
173
|
+
valueSpec: options.valueSpec as ArraySpec,
|
|
173
174
|
style: options.style,
|
|
174
175
|
styleSpec: options.styleSpec,
|
|
175
176
|
arrayElementValidator: (options) => validateAppearance(({layerType: type, layer, ...(options as object)}) as AppearanceValidatorOptions)
|
|
@@ -34,7 +34,7 @@ export default function validateLight(options: LightValidatorOptions): Validatio
|
|
|
34
34
|
const useThemeMatch = key.match(USE_THEME_KEY_RE);
|
|
35
35
|
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
37
|
-
if (useThemeMatch && lightSpec[useThemeMatch[1]]) {
|
|
37
|
+
if (useThemeMatch && lightSpec[useThemeMatch[1]!]) {
|
|
38
38
|
errors = errors.concat(validate({
|
|
39
39
|
key,
|
|
40
40
|
value: light[key],
|
|
@@ -43,7 +43,7 @@ export default function validateLight(options: LightValidatorOptions): Validatio
|
|
|
43
43
|
styleSpec
|
|
44
44
|
}));
|
|
45
45
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
46
|
-
} else if (transitionMatch && lightSpec[transitionMatch[1]] && lightSpec[transitionMatch[1]]
|
|
46
|
+
} else if (transitionMatch && lightSpec[transitionMatch[1]!] && lightSpec[transitionMatch[1]!]!.transition) {
|
|
47
47
|
errors = errors.concat(validate({
|
|
48
48
|
key,
|
|
49
49
|
value: light[key],
|
|
@@ -49,7 +49,7 @@ export default function validateLights(options: LightsValidatorOptions): Validat
|
|
|
49
49
|
if (lights) {
|
|
50
50
|
for (let i = 0; i < options.arrayIndex; i++) {
|
|
51
51
|
const lightType = unbundle(light.type);
|
|
52
|
-
const otherLight = lights[i]
|
|
52
|
+
const otherLight = lights[i]!;
|
|
53
53
|
if (unbundle(otherLight.type) === lightType) {
|
|
54
54
|
errors.push(new ValidationError(key, light.id, `duplicate light type "${light.type}", previously defined at line ${(otherLight.id as {__line__?: number}).__line__}`));
|
|
55
55
|
}
|
|
@@ -77,7 +77,7 @@ export default function validateLights(options: LightsValidatorOptions): Validat
|
|
|
77
77
|
const useThemeMatch = propertyKey.match(USE_THEME_KEY_RE);
|
|
78
78
|
|
|
79
79
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
80
|
-
if (useThemeMatch && lightPropertySpec[useThemeMatch[1]]) {
|
|
80
|
+
if (useThemeMatch && lightPropertySpec[useThemeMatch[1]!]) {
|
|
81
81
|
errors = errors.concat(validate({
|
|
82
82
|
key,
|
|
83
83
|
value: properties[propertyKey],
|
|
@@ -86,7 +86,7 @@ export default function validateLights(options: LightsValidatorOptions): Validat
|
|
|
86
86
|
styleSpec
|
|
87
87
|
}));
|
|
88
88
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
89
|
-
} else if (transitionMatch && lightPropertySpec[transitionMatch[1]] && lightPropertySpec[transitionMatch[1]].transition) {
|
|
89
|
+
} else if (transitionMatch && lightPropertySpec[transitionMatch[1]!] && lightPropertySpec[transitionMatch[1]!].transition) {
|
|
90
90
|
errors = errors.concat(validate({
|
|
91
91
|
key,
|
|
92
92
|
value: light[key],
|
|
@@ -5,10 +5,15 @@ import type {StyleReference} from '../reference/latest';
|
|
|
5
5
|
import type {StyleSpecification} from '../types';
|
|
6
6
|
import type {NumberPropertySpecification} from '../style-spec';
|
|
7
7
|
|
|
8
|
+
type NumberValueSpecification = Omit<NumberPropertySpecification, 'minimum' | 'maximum'> & {
|
|
9
|
+
minimum?: number | number[];
|
|
10
|
+
maximum?: number | number[];
|
|
11
|
+
};
|
|
12
|
+
|
|
8
13
|
type NumberValidatorOptions = {
|
|
9
14
|
key: string;
|
|
10
15
|
value: unknown;
|
|
11
|
-
valueSpec:
|
|
16
|
+
valueSpec: NumberValueSpecification;
|
|
12
17
|
style: Partial<StyleSpecification>;
|
|
13
18
|
styleSpec: StyleReference;
|
|
14
19
|
arrayIndex: number;
|
|
@@ -29,11 +34,10 @@ export default function validateNumber(options: NumberValidatorOptions): Validat
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
if ('minimum' in valueSpec) {
|
|
32
|
-
let specMin = valueSpec.minimum;
|
|
37
|
+
let specMin = valueSpec.minimum as number;
|
|
33
38
|
if (Array.isArray(valueSpec.minimum)) {
|
|
34
39
|
const i = options.arrayIndex;
|
|
35
|
-
|
|
36
|
-
specMin = valueSpec.minimum[i];
|
|
40
|
+
specMin = valueSpec.minimum[i]!;
|
|
37
41
|
}
|
|
38
42
|
if (value < specMin) {
|
|
39
43
|
return [new ValidationError(key, value, `${value} is less than the minimum value ${specMin}`)];
|
|
@@ -41,11 +45,10 @@ export default function validateNumber(options: NumberValidatorOptions): Validat
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
if ('maximum' in valueSpec) {
|
|
44
|
-
let specMax = valueSpec.maximum;
|
|
48
|
+
let specMax = valueSpec.maximum as number;
|
|
45
49
|
if (Array.isArray(valueSpec.maximum)) {
|
|
46
50
|
const i = options.arrayIndex;
|
|
47
|
-
|
|
48
|
-
specMax = valueSpec.maximum[i];
|
|
51
|
+
specMax = valueSpec.maximum[i]!;
|
|
49
52
|
}
|
|
50
53
|
if (value > specMax) {
|
|
51
54
|
return [new ValidationError(key, value, `${value} is greater than the maximum value ${specMax}`)];
|
|
@@ -15,16 +15,18 @@ type ObjectElementValidatorOptions = {
|
|
|
15
15
|
objectKey?: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
export type ObjectElementValidator = (options: ObjectElementValidatorOptions, object?: object) => ValidationError[];
|
|
19
|
+
|
|
18
20
|
type ObjectValidatorOptions = {
|
|
19
21
|
key: string;
|
|
20
22
|
value: unknown;
|
|
21
|
-
valueSpec?:
|
|
23
|
+
valueSpec?: Record<PropertyKey, unknown>;
|
|
22
24
|
style: Partial<StyleSpecification>;
|
|
23
25
|
styleSpec: StyleReference;
|
|
24
26
|
object?: object;
|
|
25
27
|
objectKey?: string;
|
|
26
28
|
layer?: LayerSpecification;
|
|
27
|
-
objectElementValidators?: Record<string,
|
|
29
|
+
objectElementValidators?: Record<string, ObjectElementValidator>;
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
export default function validateObject(options: ObjectValidatorOptions): ValidationError[] {
|
|
@@ -47,25 +49,24 @@ export default function validateObject(options: ObjectValidatorOptions): Validat
|
|
|
47
49
|
// inherited properties as if they were user-supplied.
|
|
48
50
|
if (!Object.hasOwn(object, objectKey)) continue;
|
|
49
51
|
|
|
50
|
-
const elementSpecKey = objectKey.split('.')[0]
|
|
52
|
+
const elementSpecKey = objectKey.split('.')[0]!; // treat 'paint.*' as 'paint'
|
|
51
53
|
// Object.hasOwn: a bare lookup like `elementSpecs[objectKey]` would
|
|
52
54
|
// find inherited keys from Object.prototype (e.g. "__proto__",
|
|
53
55
|
// "constructor", "toString") when the user-supplied key matches one,
|
|
54
56
|
// returning a non-spec value that then fails as a validator.
|
|
55
57
|
const hasSpec = Object.hasOwn(elementSpecs, elementSpecKey);
|
|
56
58
|
const hasWildcardSpec = Object.hasOwn(elementSpecs, '*');
|
|
57
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
58
59
|
const elementSpec = hasSpec ? elementSpecs[elementSpecKey] : (hasWildcardSpec ? elementSpecs['*'] : undefined);
|
|
59
60
|
|
|
60
|
-
let validateElement:
|
|
61
|
+
let validateElement: ObjectElementValidator | undefined;
|
|
61
62
|
if (Object.hasOwn(elementValidators, elementSpecKey)) {
|
|
62
|
-
validateElement = elementValidators[elementSpecKey]
|
|
63
|
+
validateElement = elementValidators[elementSpecKey]!;
|
|
63
64
|
} else if (hasSpec) {
|
|
64
|
-
validateElement = validateSpec;
|
|
65
|
+
validateElement = validateSpec as ObjectElementValidator;
|
|
65
66
|
} else if (Object.hasOwn(elementValidators, '*')) {
|
|
66
|
-
validateElement = elementValidators['*']
|
|
67
|
+
validateElement = elementValidators['*']!;
|
|
67
68
|
} else if (hasWildcardSpec) {
|
|
68
|
-
validateElement = validateSpec;
|
|
69
|
+
validateElement = validateSpec as ObjectElementValidator;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
if (!validateElement) {
|
|
@@ -91,7 +92,7 @@ export default function validateObject(options: ObjectValidatorOptions): Validat
|
|
|
91
92
|
continue;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
const elementSpec = elementSpecs[elementSpecKey] as {required?: boolean; default?: unknown};
|
|
95
|
+
const elementSpec = elementSpecs[elementSpecKey]! as {required?: boolean; default?: unknown};
|
|
95
96
|
if (elementSpec.required && elementSpec['default'] === undefined && (!Object.hasOwn(object, elementSpecKey) || object[elementSpecKey] === undefined)) {
|
|
96
97
|
errors.push(new ValidationError(key, object, `missing required property "${elementSpecKey}"`));
|
|
97
98
|
}
|
|
@@ -5,6 +5,7 @@ import {isObject} from '../util/get_type';
|
|
|
5
5
|
|
|
6
6
|
import type ValidationError from '../error/validation_error';
|
|
7
7
|
import type {ValidatorOptions} from './validate';
|
|
8
|
+
import type {ObjectElementValidator} from './validate_object';
|
|
8
9
|
|
|
9
10
|
// Validator for a single config option (one entry in `schema`). Wraps the
|
|
10
11
|
// generic object validator to inherit the option's declared `type` when
|
|
@@ -19,18 +20,32 @@ export default function validateOption(options: ValidatorOptions): ValidationErr
|
|
|
19
20
|
const isArrayOption = isObject(optionValue) && unbundle(optionValue.array) === true;
|
|
20
21
|
const declaredType = !isArrayOption && isObject(optionValue) ? unbundle(optionValue.type) : undefined;
|
|
21
22
|
|
|
23
|
+
const validateDefault = (elementOptions: ValidatorOptions): ValidationError[] => {
|
|
24
|
+
const defaultValue = elementOptions.value;
|
|
25
|
+
if (isObject(defaultValue)) {
|
|
26
|
+
// A plain object default (e.g. a GeoJSON geometry for an
|
|
27
|
+
// `object`-typed option) isn't an expression or a legacy
|
|
28
|
+
// function spec -- validate it as a bare value like a runtime
|
|
29
|
+
// `config` value, instead of running it through
|
|
30
|
+
// validateFunction()/validateExpression() (see `validate()`).
|
|
31
|
+
return validateSpec({...elementOptions, valueSpec: {...elementOptions.valueSpec, type: '*', expression: undefined} as unknown as typeof elementOptions.valueSpec});
|
|
32
|
+
}
|
|
33
|
+
// Only propagate the declared type when the default is an
|
|
34
|
+
// expression (array-form). Primitive defaults keep the previous
|
|
35
|
+
// permissive validation, mirroring the runtime parser's narrowing
|
|
36
|
+
// in style.ts/parser.cpp.
|
|
37
|
+
if (declaredType && Array.isArray(defaultValue)) {
|
|
38
|
+
return validateSpec({...elementOptions, valueSpec: {...elementOptions.valueSpec, type: declaredType} as typeof elementOptions.valueSpec});
|
|
39
|
+
}
|
|
40
|
+
return validateSpec(elementOptions);
|
|
41
|
+
};
|
|
42
|
+
|
|
22
43
|
return validateObject({
|
|
23
44
|
...options,
|
|
24
45
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
25
46
|
valueSpec: styleSpec.option,
|
|
26
|
-
objectElementValidators:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// permissive validation, mirroring the runtime parser's narrowing
|
|
30
|
-
// in style.ts/parser.cpp.
|
|
31
|
-
default: (elementOptions: ValidatorOptions): ValidationError[] => (Array.isArray(elementOptions.value) ?
|
|
32
|
-
validateSpec({...elementOptions, valueSpec: {...elementOptions.valueSpec, type: declaredType} as typeof elementOptions.valueSpec}) :
|
|
33
|
-
validateSpec(elementOptions)),
|
|
34
|
-
} : undefined,
|
|
47
|
+
objectElementValidators: {
|
|
48
|
+
default: validateDefault,
|
|
49
|
+
} as Record<string, ObjectElementValidator>,
|
|
35
50
|
});
|
|
36
51
|
}
|
|
@@ -31,13 +31,13 @@ export default function validateProperty(options: PropertyValidatorOptions, prop
|
|
|
31
31
|
const layer = options.layer;
|
|
32
32
|
const styleSpec = options.styleSpec;
|
|
33
33
|
const value = options.value;
|
|
34
|
-
const propertyKey = options.objectKey
|
|
34
|
+
const propertyKey = options.objectKey!;
|
|
35
35
|
const layerSpec = styleSpec[`${propertyType}_${options.layerType}`] as Record<string, StylePropertySpecification> | undefined;
|
|
36
36
|
|
|
37
37
|
if (!layerSpec) return [];
|
|
38
38
|
|
|
39
39
|
const useThemeMatch = propertyKey.match(USE_THEME_KEY_RE);
|
|
40
|
-
if (useThemeMatch && layerSpec[useThemeMatch[1]]) {
|
|
40
|
+
if (useThemeMatch && layerSpec[useThemeMatch[1]!]) {
|
|
41
41
|
if (isExpression(deepUnbundle(value))) {
|
|
42
42
|
const errors: ValidationError[] = [];
|
|
43
43
|
return errors.concat(validate({
|
|
@@ -69,7 +69,7 @@ export default function validateProperty(options: PropertyValidatorOptions, prop
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const transitionMatch = propertyKey.match(TRANSITION_KEY_RE);
|
|
72
|
-
if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]] && layerSpec[transitionMatch[1]]
|
|
72
|
+
if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]!] && layerSpec[transitionMatch[1]!]!.transition) {
|
|
73
73
|
return validate({
|
|
74
74
|
key,
|
|
75
75
|
value,
|
|
@@ -85,7 +85,7 @@ export default function validateProperty(options: PropertyValidatorOptions, prop
|
|
|
85
85
|
return [new ValidationWarning(key, value, `unknown property "${propertyKey}"`)];
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
let tokenMatch: RegExpExecArray | undefined;
|
|
88
|
+
let tokenMatch: RegExpExecArray | null | undefined;
|
|
89
89
|
if (isString(value) && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = TOKEN_PATTERN_RE.exec(value))) {
|
|
90
90
|
const example = `\`{ "type": "identity", "property": ${tokenMatch ? JSON.stringify(tokenMatch[1]) : '"_"'} }\``;
|
|
91
91
|
return [new ValidationError(
|
|
@@ -32,7 +32,7 @@ export default function validateRain(options: RainValidatorOptions): ValidationE
|
|
|
32
32
|
for (const key in rain) {
|
|
33
33
|
const transitionMatch = key.match(TRANSITION_KEY_RE);
|
|
34
34
|
|
|
35
|
-
if (transitionMatch && rainSpec[transitionMatch[1]] && rainSpec[transitionMatch[1]]
|
|
35
|
+
if (transitionMatch && rainSpec[transitionMatch[1]!] && rainSpec[transitionMatch[1]!]!.transition) {
|
|
36
36
|
errors = errors.concat(validate({
|
|
37
37
|
key,
|
|
38
38
|
value: rain[key],
|
|
@@ -32,7 +32,7 @@ export default function validateSnow(options: SnowValidatorOptions): ValidationE
|
|
|
32
32
|
for (const key in snow) {
|
|
33
33
|
const transitionMatch = key.match(TRANSITION_KEY_RE);
|
|
34
34
|
|
|
35
|
-
if (transitionMatch && snowSpec[transitionMatch[1]] && snowSpec[transitionMatch[1]]
|
|
35
|
+
if (transitionMatch && snowSpec[transitionMatch[1]!] && snowSpec[transitionMatch[1]!]!.transition) {
|
|
36
36
|
errors = errors.concat(validate({
|
|
37
37
|
key,
|
|
38
38
|
value: snow[key],
|
|
@@ -4,6 +4,7 @@ import validateGlyphsURL from './validate_glyphs_url';
|
|
|
4
4
|
|
|
5
5
|
import type ValidationError from '../error/validation_error';
|
|
6
6
|
import type {StyleReference} from '../reference/latest';
|
|
7
|
+
import type {StyleSpecification} from '../types';
|
|
7
8
|
|
|
8
9
|
type StyleValidatorOptions = {
|
|
9
10
|
key?: string;
|
|
@@ -20,7 +21,7 @@ export default function validateStyle(style: unknown, styleSpec: StyleReference
|
|
|
20
21
|
'*': {type: '*'},
|
|
21
22
|
},
|
|
22
23
|
styleSpec,
|
|
23
|
-
style
|
|
24
|
+
style: style as Partial<StyleSpecification>,
|
|
24
25
|
objectElementValidators: {
|
|
25
26
|
glyphs: validateGlyphsURL
|
|
26
27
|
}
|
|
@@ -36,7 +36,7 @@ export default function validateTerrain(options: TerrainValidatorOptions): Valid
|
|
|
36
36
|
const useThemeMatch = key.match(USE_THEME_KEY_RE);
|
|
37
37
|
|
|
38
38
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
39
|
-
if (useThemeMatch && terrainSpec[useThemeMatch[1]]) {
|
|
39
|
+
if (useThemeMatch && terrainSpec[useThemeMatch[1]!]) {
|
|
40
40
|
errors = errors.concat(validate({
|
|
41
41
|
key,
|
|
42
42
|
value: terrain[key],
|
|
@@ -45,7 +45,7 @@ export default function validateTerrain(options: TerrainValidatorOptions): Valid
|
|
|
45
45
|
styleSpec
|
|
46
46
|
}));
|
|
47
47
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
48
|
-
} else if (transitionMatch && terrainSpec[transitionMatch[1]] && terrainSpec[transitionMatch[1]].transition) {
|
|
48
|
+
} else if (transitionMatch && terrainSpec[transitionMatch[1]!] && terrainSpec[transitionMatch[1]!].transition) {
|
|
49
49
|
errors = errors.concat(validate({
|
|
50
50
|
key,
|
|
51
51
|
value: terrain[key],
|
|
@@ -33,13 +33,13 @@ function isValid(value: string | null | undefined, regex: RegExp): boolean {
|
|
|
33
33
|
|
|
34
34
|
function getSourceCount(source: SourceSpecification): number {
|
|
35
35
|
if ('url' in source) {
|
|
36
|
-
return source.url
|
|
36
|
+
return source.url!.split(',').length;
|
|
37
37
|
} else {
|
|
38
38
|
return 0;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function getAllowedKeyErrors(obj:
|
|
42
|
+
function getAllowedKeyErrors(obj: Record<string, unknown>, keys: string[], path?: string | null): Array<ValidationError> {
|
|
43
43
|
const allowed = new Set(keys);
|
|
44
44
|
const errors: ValidationError[] = [];
|
|
45
45
|
Object.keys(obj).forEach(k => {
|
|
@@ -87,21 +87,20 @@ function getSourcesErrors(sources: SourcesSpecification): {
|
|
|
87
87
|
errors: Array<ValidationError>;
|
|
88
88
|
sourcesCount: number;
|
|
89
89
|
} {
|
|
90
|
-
const errors = [];
|
|
90
|
+
const errors: ValidationError[] = [];
|
|
91
91
|
let sourcesCount = 0;
|
|
92
92
|
|
|
93
93
|
Object.keys(sources).forEach((s: string, i: number) => {
|
|
94
|
-
const sourceErrors = getSourceErrors(sources[s]
|
|
94
|
+
const sourceErrors = getSourceErrors(sources[s]!, i);
|
|
95
95
|
|
|
96
96
|
// If source has errors, skip counting
|
|
97
97
|
if (!sourceErrors.length) {
|
|
98
|
-
sourcesCount = sourcesCount + getSourceCount(sources[s]);
|
|
98
|
+
sourcesCount = sourcesCount + getSourceCount(sources[s]!);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
errors.push(...sourceErrors);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
105
104
|
return {errors, sourcesCount};
|
|
106
105
|
}
|
|
107
106
|
|
package/validate_style.min.ts
CHANGED
|
@@ -23,7 +23,8 @@ export type ValidationError = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export type ValidationErrors = ReadonlyArray<ValidationError>;
|
|
26
|
-
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
export type Validator<T extends (...args: any[]) => unknown = (...args: unknown[]) => unknown> = (...args: Parameters<T>) => ValidationErrors;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Validate a Mapbox GL style against the style specification. This entrypoint,
|
package/visit.ts
CHANGED
|
@@ -8,17 +8,18 @@ import type {
|
|
|
8
8
|
PropertyValueSpecification
|
|
9
9
|
} from './types';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type ReferenceSection = Record<string, StylePropertySpecification>;
|
|
12
|
+
type ReferenceWithSections = typeof Reference & Record<string, ReferenceSection>;
|
|
13
|
+
|
|
14
|
+
function getPropertyReference(propertyName: string): StylePropertySpecification | null {
|
|
12
15
|
for (let i = 0; i < Reference.layout.length; i++) {
|
|
13
|
-
for (const key in Reference[Reference.layout[i]]) {
|
|
14
|
-
|
|
15
|
-
if (key === propertyName) return Reference[Reference.layout[i]][key] as StylePropertySpecification;
|
|
16
|
+
for (const key in (Reference as ReferenceWithSections)[Reference.layout[i]!]) {
|
|
17
|
+
if (key === propertyName) return (Reference as ReferenceWithSections)[Reference.layout[i]!]![key]!;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
for (let i = 0; i < Reference.paint.length; i++) {
|
|
19
|
-
for (const key in Reference[Reference.paint[i]]) {
|
|
20
|
-
|
|
21
|
-
if (key === propertyName) return Reference[Reference.paint[i]][key] as StylePropertySpecification;
|
|
21
|
+
for (const key in (Reference as ReferenceWithSections)[Reference.paint[i]!]) {
|
|
22
|
+
if (key === propertyName) return (Reference as ReferenceWithSections)[Reference.paint[i]!]![key]!;
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -27,7 +28,7 @@ function getPropertyReference(propertyName: string): StylePropertySpecification
|
|
|
27
28
|
|
|
28
29
|
export function eachSource(style: StyleSpecification, callback: (_: SourceSpecification) => void) {
|
|
29
30
|
for (const k in style.sources) {
|
|
30
|
-
callback(style.sources[k]);
|
|
31
|
+
callback(style.sources[k]!);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -59,14 +60,14 @@ export function eachProperty(
|
|
|
59
60
|
) {
|
|
60
61
|
function inner(layer: LayerSpecification, propertyType: 'paint' | 'layout') {
|
|
61
62
|
if (layer.type === 'slot' || layer.type === 'clip') return;
|
|
62
|
-
const properties = layer[propertyType];
|
|
63
|
+
const properties: Record<string, PropertyValueSpecification<unknown>> | undefined = layer[propertyType];
|
|
63
64
|
if (!properties) return;
|
|
64
65
|
Object.keys(properties).forEach((key) => {
|
|
65
66
|
callback({
|
|
66
67
|
path: [layer.id, propertyType, key],
|
|
67
68
|
key,
|
|
68
69
|
value: properties[key],
|
|
69
|
-
reference: getPropertyReference(key)
|
|
70
|
+
reference: getPropertyReference(key)!,
|
|
70
71
|
set(x) {
|
|
71
72
|
properties[key] = x;
|
|
72
73
|
}
|