@mapbox/mapbox-gl-style-spec 14.24.1 → 14.25.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/bin/gl-style-composite.js +9 -4
- package/bin/gl-style-format.js +10 -4
- package/bin/gl-style-migrate.js +9 -4
- package/bin/gl-style-validate.js +16 -15
- package/deref.ts +8 -6
- package/dist/index.cjs +7743 -9443
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -11
- package/dist/index.es.js +7743 -9443
- package/dist/index.es.js.map +1 -1
- package/expression/compound_expression.ts +1 -1
- package/expression/definitions/assertion.ts +1 -1
- package/expression/definitions/at_interpolated.ts +2 -1
- package/expression/definitions/case.ts +1 -1
- package/expression/definitions/coalesce.ts +1 -1
- package/expression/definitions/coercion.ts +1 -1
- package/expression/definitions/distance.ts +1 -9
- package/expression/definitions/interpolate.ts +3 -3
- package/expression/definitions/literal.ts +1 -1
- package/expression/definitions/match.ts +1 -1
- package/expression/definitions/number_format.ts +8 -6
- package/expression/definitions/within.ts +1 -8
- package/expression/evaluation_context.ts +1 -10
- package/expression/index.ts +1 -1
- package/expression/values.ts +3 -4
- package/feature_filter/index.ts +1 -1
- package/function/convert.ts +1 -1
- package/group_by_layout.ts +8 -2
- package/package.json +4 -6
- package/read_style.ts +4 -4
- package/reference/v8.json +20 -7
- package/rollup.config.js +7 -4
- package/test.js +1 -1
- package/types/config_options.ts +7 -7
- package/types/lut.ts +5 -5
- package/types/tile_id.ts +3 -3
- package/types.ts +3 -1
- package/union-to-intersection.ts +2 -2
- package/util/assert.ts +3 -0
- package/util/color.ts +33 -18
- package/util/deep_equal.ts +6 -4
- package/util/geometry_util.ts +1 -1
- package/util/lerp.ts +3 -1
- package/util/mercator.ts +16 -0
- package/util/unbundle_jsonlint.ts +3 -2
- package/validate/validate.ts +2 -0
- package/validate/validate_appearance.ts +1 -1
- package/validate/validate_color.ts +5 -2
- package/validate/validate_expression.ts +16 -8
- package/validate/validate_function.ts +3 -2
- package/validate/validate_iconset.ts +1 -1
- package/validate/validate_import.ts +7 -0
- package/validate/validate_layer.ts +4 -1
- package/validate/validate_object.ts +20 -7
- package/validate/validate_option.ts +37 -0
- package/validate/validate_terrain.ts +1 -1
- package/validate_style.ts +3 -3
|
@@ -14,8 +14,9 @@ export function deepUnbundle(value: unknown): unknown {
|
|
|
14
14
|
const unbundledValue: {
|
|
15
15
|
[key: string]: unknown;
|
|
16
16
|
} = {};
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const valueRec = value as Record<string, unknown>;
|
|
18
|
+
for (const key in valueRec) {
|
|
19
|
+
unbundledValue[key] = deepUnbundle(valueRec[key]);
|
|
19
20
|
}
|
|
20
21
|
return unbundledValue;
|
|
21
22
|
}
|
package/validate/validate.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {unbundle, deepUnbundle} from '../util/unbundle_jsonlint';
|
|
|
2
2
|
import {isExpression} from '../expression/index';
|
|
3
3
|
import {isFunction} from '../function/index';
|
|
4
4
|
import validateImport from './validate_import';
|
|
5
|
+
import validateOption from './validate_option';
|
|
5
6
|
import validateFunction from './validate_function';
|
|
6
7
|
import validateExpression from './validate_expression';
|
|
7
8
|
import validateObject from './validate_object';
|
|
@@ -54,6 +55,7 @@ const VALIDATORS: Record<string, (...args: unknown[]) => ValidationError[]> = {
|
|
|
54
55
|
'projection': validateProjection,
|
|
55
56
|
'import': validateImport,
|
|
56
57
|
'iconset': validateIconset,
|
|
58
|
+
'option': validateOption,
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
export type ValidatorOptions = {
|
|
@@ -90,7 +90,7 @@ function validateCondition(options: AppearanceValidatorOptions): Array<Validatio
|
|
|
90
90
|
errors.push(...validateExpression({
|
|
91
91
|
key: options.key,
|
|
92
92
|
value: condition,
|
|
93
|
-
valueSpec: (latest['appearance'] as Record<string, unknown>)['condition']
|
|
93
|
+
valueSpec: (latest['appearance'] as Record<string, unknown>)['condition'],
|
|
94
94
|
expressionContext: 'appearance'
|
|
95
95
|
}));
|
|
96
96
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ValidationError from '../error/validation_error';
|
|
2
2
|
import {getType, isString} from '../util/get_type';
|
|
3
|
-
import {
|
|
3
|
+
import {unbundle} from '../util/unbundle_jsonlint';
|
|
4
|
+
import Color from '../util/color';
|
|
4
5
|
|
|
5
6
|
type ColorValidatorOptions = {
|
|
6
7
|
key: string;
|
|
@@ -12,7 +13,9 @@ export default function validateColor({key, value}: ColorValidatorOptions): Vali
|
|
|
12
13
|
return [new ValidationError(key, value, `color expected, ${getType(value)} found`)];
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
// `value` may be a jsonlint-lines-primitives `String` wrapper; unbundle to a
|
|
17
|
+
// primitive so `Color.parse`'s `typeof input === 'string'` check passes.
|
|
18
|
+
if (Color.parse(unbundle(value) as string) === undefined) {
|
|
16
19
|
return [new ValidationError(key, value, `color expected, "${value}" found`)];
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -50,7 +50,7 @@ export default function validateExpression(options: ExpressionValidatorOptions):
|
|
|
50
50
|
|
|
51
51
|
if (options.expressionContext === 'appearance') {
|
|
52
52
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
53
|
-
return
|
|
53
|
+
return checkDisallowedAppearancesParameters(expressionObj, options);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (options.expressionContext && options.expressionContext.indexOf('cluster') === 0) {
|
|
@@ -103,32 +103,40 @@ export function disallowedFilterParameters(e: Expression, options: any): Validat
|
|
|
103
103
|
return errors;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
const FEATURE_OPERATORS = new Set(['get', 'has', 'properties', 'geometry-type', 'id']);
|
|
107
|
+
const APPEARANCES_DISALLOWED_PARAMETERS = new Set([
|
|
108
|
+
'zoom', 'pitch', 'distance-from-center',
|
|
109
|
+
'feature', 'feature-state',
|
|
110
|
+
'measure-light',
|
|
111
|
+
'heatmap-density', 'line-progress',
|
|
112
|
+
'raster-value', 'raster-particle-speed', 'sky-radial-progress',
|
|
113
|
+
]);
|
|
109
114
|
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
function checkDisallowedAppearancesParameters(e: Expression, options: any): ValidationError[] {
|
|
110
117
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
111
118
|
if (options.valueSpec && options.valueSpec.expression) {
|
|
112
119
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
113
120
|
for (const param of options.valueSpec.expression.parameters) {
|
|
114
121
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
115
|
-
|
|
122
|
+
APPEARANCES_DISALLOWED_PARAMETERS.delete(param);
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
if (
|
|
126
|
+
if (APPEARANCES_DISALLOWED_PARAMETERS.size === 0) {
|
|
120
127
|
return [];
|
|
121
128
|
}
|
|
122
129
|
const errors: ValidationError[] = [];
|
|
123
130
|
|
|
124
131
|
if (e instanceof CompoundExpression) {
|
|
125
|
-
|
|
132
|
+
const param = FEATURE_OPERATORS.has(e.name) ? 'feature' : e.name;
|
|
133
|
+
if (APPEARANCES_DISALLOWED_PARAMETERS.has(param)) {
|
|
126
134
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
|
|
127
135
|
return [new ValidationError(options.key, options.value, `["${e.name}"] is not an allowed parameter`)];
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
e.eachChild((arg) => {
|
|
131
|
-
errors.push(...
|
|
139
|
+
errors.push(...checkDisallowedAppearancesParameters(arg, options));
|
|
132
140
|
});
|
|
133
141
|
|
|
134
142
|
return errors;
|
|
@@ -99,7 +99,7 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
99
99
|
errors = errors.concat(validateArray({
|
|
100
100
|
key: options.key,
|
|
101
101
|
value,
|
|
102
|
-
valueSpec: options.valueSpec
|
|
102
|
+
valueSpec: options.valueSpec,
|
|
103
103
|
style: options.style,
|
|
104
104
|
styleSpec: options.styleSpec,
|
|
105
105
|
arrayElementValidator: validateFunctionStop
|
|
@@ -207,7 +207,8 @@ export default function validateFunction(options: FunctionValidatorOptions): Val
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
if (functionType === 'categorical' && type === 'number' && (typeof value !== 'number' || !isFinite(value) || Math.floor(value) !== value)) {
|
|
210
|
-
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to number|string|boolean by the check above
|
|
211
|
+
return [new ValidationError(options.key, reportValue, `integer expected, found ${String(value)}`)];
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
if (functionType !== 'categorical' && type === 'number' && typeof value === 'number' && typeof previousStopDomainValue === 'number' && previousStopDomainValue !== undefined && value < previousStopDomainValue) {
|
|
@@ -45,7 +45,7 @@ export default function validateIconset(options: IconsetValidatorOptions): Valid
|
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
47
|
if (isSourceIconset(type, iconset)) {
|
|
48
|
-
const source = style.sources && style.sources[iconset.source];
|
|
48
|
+
const source = style.sources && Object.hasOwn(style.sources, iconset.source) ? style.sources[iconset.source] : undefined;
|
|
49
49
|
const sourceType = source && unbundle(source.type) as string;
|
|
50
50
|
if (!source) {
|
|
51
51
|
errors.push(new ValidationError(key, iconset.source, `source "${iconset.source}" not found`));
|
|
@@ -42,6 +42,13 @@ export default function validateImport(options: ImportValidatorOptions): Validat
|
|
|
42
42
|
errors.push(new ValidationError(key, importSpec, `import id can't be an empty string`));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Reject reserved prototype-pollution key — the import id is used as a scope
|
|
46
|
+
// string that becomes a dictionary key in several runtime caches.
|
|
47
|
+
if (unbundle(importSpec.id) === '__proto__') {
|
|
48
|
+
const key = `${options.key}.id`;
|
|
49
|
+
errors.push(new ValidationError(key, importSpec, `import id can't be "__proto__"`));
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
if (data) {
|
|
46
53
|
const key = `${options.key}.data`;
|
|
47
54
|
errors = errors.concat(validateStyle(data, styleSpec, {key}));
|
|
@@ -77,7 +77,10 @@ export default function validateLayer(options: LayerValidatorOptions): Validatio
|
|
|
77
77
|
} else if (!isString(layer.source)) {
|
|
78
78
|
errors.push(new ValidationError(`${key}.source`, layer.source, '"source" must be a string'));
|
|
79
79
|
} else {
|
|
80
|
-
|
|
80
|
+
// Object.hasOwn: a bare lookup like `style.sources[layer.source]` would
|
|
81
|
+
// find inherited keys from Object.prototype (e.g. "constructor", "toString")
|
|
82
|
+
// and treat them as valid sources.
|
|
83
|
+
const source = style.sources && Object.hasOwn(style.sources, layer.source) ? style.sources[layer.source] : undefined;
|
|
81
84
|
const sourceType = source && unbundle(source.type);
|
|
82
85
|
if (!source) {
|
|
83
86
|
errors.push(new ValidationError(key, layer.source, `source "${layer.source}" not found`));
|
|
@@ -41,18 +41,30 @@ export default function validateObject(options: ObjectValidatorOptions): Validat
|
|
|
41
41
|
|
|
42
42
|
let errors: ValidationError[] = [];
|
|
43
43
|
for (const objectKey in object) {
|
|
44
|
+
// Skip inherited keys: a hostile style JSON parsed with JSON.parse can
|
|
45
|
+
// have an own "__proto__" key, but downstream consumers may have mutated
|
|
46
|
+
// the prototype of `object` upstream. We never want to validate
|
|
47
|
+
// inherited properties as if they were user-supplied.
|
|
48
|
+
if (!Object.hasOwn(object, objectKey)) continue;
|
|
49
|
+
|
|
44
50
|
const elementSpecKey = objectKey.split('.')[0]; // treat 'paint.*' as 'paint'
|
|
51
|
+
// Object.hasOwn: a bare lookup like `elementSpecs[objectKey]` would
|
|
52
|
+
// find inherited keys from Object.prototype (e.g. "__proto__",
|
|
53
|
+
// "constructor", "toString") when the user-supplied key matches one,
|
|
54
|
+
// returning a non-spec value that then fails as a validator.
|
|
55
|
+
const hasSpec = Object.hasOwn(elementSpecs, elementSpecKey);
|
|
56
|
+
const hasWildcardSpec = Object.hasOwn(elementSpecs, '*');
|
|
45
57
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
46
|
-
const elementSpec = elementSpecs[elementSpecKey]
|
|
58
|
+
const elementSpec = hasSpec ? elementSpecs[elementSpecKey] : (hasWildcardSpec ? elementSpecs['*'] : undefined);
|
|
47
59
|
|
|
48
60
|
let validateElement: ((options: ObjectElementValidatorOptions, object?: unknown) => ValidationError[]) | undefined;
|
|
49
|
-
if (elementValidators
|
|
61
|
+
if (Object.hasOwn(elementValidators, elementSpecKey)) {
|
|
50
62
|
validateElement = elementValidators[elementSpecKey];
|
|
51
|
-
} else if (
|
|
63
|
+
} else if (hasSpec) {
|
|
52
64
|
validateElement = validateSpec;
|
|
53
|
-
} else if (elementValidators
|
|
65
|
+
} else if (Object.hasOwn(elementValidators, '*')) {
|
|
54
66
|
validateElement = elementValidators['*'];
|
|
55
|
-
} else if (
|
|
67
|
+
} else if (hasWildcardSpec) {
|
|
56
68
|
validateElement = validateSpec;
|
|
57
69
|
}
|
|
58
70
|
|
|
@@ -73,13 +85,14 @@ export default function validateObject(options: ObjectValidatorOptions): Validat
|
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
for (const elementSpecKey in elementSpecs) {
|
|
88
|
+
if (!Object.hasOwn(elementSpecs, elementSpecKey)) continue;
|
|
76
89
|
// Don't check `required` when there's a custom validator for that property.
|
|
77
|
-
if (elementValidators
|
|
90
|
+
if (Object.hasOwn(elementValidators, elementSpecKey)) {
|
|
78
91
|
continue;
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
const elementSpec = elementSpecs[elementSpecKey] as {required?: boolean; default?: unknown};
|
|
82
|
-
if (elementSpec.required && elementSpec['default'] === undefined && object[elementSpecKey] === undefined) {
|
|
95
|
+
if (elementSpec.required && elementSpec['default'] === undefined && (!Object.hasOwn(object, elementSpecKey) || object[elementSpecKey] === undefined)) {
|
|
83
96
|
errors.push(new ValidationError(key, object, `missing required property "${elementSpecKey}"`));
|
|
84
97
|
}
|
|
85
98
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import validateSpec from './validate';
|
|
2
|
+
import validateObject from './validate_object';
|
|
3
|
+
import {unbundle} from '../util/unbundle_jsonlint';
|
|
4
|
+
import {isObject} from '../util/get_type';
|
|
5
|
+
|
|
6
|
+
import type ValidationError from '../error/validation_error';
|
|
7
|
+
import type {ValidatorOptions} from './validate';
|
|
8
|
+
|
|
9
|
+
// Validator for a single config option (one entry in `schema`). Wraps the
|
|
10
|
+
// generic object validator to inherit the option's declared `type` when
|
|
11
|
+
// validating its `default`, so expressions in the default are checked against
|
|
12
|
+
// the option's expected type instead of `*` (any).
|
|
13
|
+
export default function validateOption(options: ValidatorOptions): ValidationError[] {
|
|
14
|
+
const styleSpec = options.styleSpec;
|
|
15
|
+
const optionValue = options.value as {type?: unknown; array?: unknown} | null | undefined;
|
|
16
|
+
// Don't propagate the declared type for array options: `getExpectedType`
|
|
17
|
+
// doesn't model the schema's `array: true` flag, so forcing a scalar type
|
|
18
|
+
// here would reject an array-of-T default that previously validated fine.
|
|
19
|
+
const isArrayOption = isObject(optionValue) && unbundle(optionValue.array) === true;
|
|
20
|
+
const declaredType = !isArrayOption && isObject(optionValue) ? unbundle(optionValue.type) : undefined;
|
|
21
|
+
|
|
22
|
+
return validateObject(Object.assign({}, options, {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
24
|
+
valueSpec: styleSpec.option,
|
|
25
|
+
objectElementValidators: declaredType ? {
|
|
26
|
+
// Only propagate the declared type when the default is an
|
|
27
|
+
// expression (array-form). Primitive defaults keep the previous
|
|
28
|
+
// permissive validation, mirroring the runtime parser's narrowing
|
|
29
|
+
// in style.ts/parser.cpp.
|
|
30
|
+
default: (elementOptions: ValidatorOptions): ValidationError[] => (Array.isArray(elementOptions.value) ?
|
|
31
|
+
validateSpec(Object.assign({}, elementOptions, {
|
|
32
|
+
valueSpec: Object.assign({}, elementOptions.valueSpec, {type: declaredType}),
|
|
33
|
+
})) :
|
|
34
|
+
validateSpec(elementOptions)),
|
|
35
|
+
} : undefined,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
@@ -74,7 +74,7 @@ export default function validateTerrain(options: TerrainValidatorOptions): Valid
|
|
|
74
74
|
} else if (!isString(terrain.source)) {
|
|
75
75
|
errors.push(new ValidationError(`${key}.source`, terrain.source, `source must be a string`));
|
|
76
76
|
} else {
|
|
77
|
-
const source = style.sources && style.sources[terrain.source];
|
|
77
|
+
const source = style.sources && Object.hasOwn(style.sources, terrain.source) ? style.sources[terrain.source] : undefined;
|
|
78
78
|
const sourceType = source && unbundle(source.type) as string;
|
|
79
79
|
if (!source) {
|
|
80
80
|
errors.push(new ValidationError(`${key}.source`, terrain.source, `source "${terrain.source}" not found`));
|
package/validate_style.ts
CHANGED
|
@@ -11,8 +11,8 @@ import type {StyleSpecification} from './types';
|
|
|
11
11
|
*
|
|
12
12
|
* @private
|
|
13
13
|
* @alias validate
|
|
14
|
-
* @param {Object|String|
|
|
15
|
-
* or `
|
|
14
|
+
* @param {Object|String|Uint8Array} style The style to be validated. If a `String`
|
|
15
|
+
* or `Uint8Array` is provided, the returned errors will contain line numbers.
|
|
16
16
|
* @param {Object} [styleSpec] The style specification to validate against.
|
|
17
17
|
* If omitted, the spec version is inferred from the stylesheet.
|
|
18
18
|
* @returns {Array<ValidationError|ParsingError>}
|
|
@@ -22,7 +22,7 @@ import type {StyleSpecification} from './types';
|
|
|
22
22
|
* var errors = validate(style);
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
export default function validateStyle(style: StyleSpecification | string |
|
|
25
|
+
export default function validateStyle(style: StyleSpecification | string | Uint8Array, styleSpec: StyleReference = v8): ValidationErrors {
|
|
26
26
|
let s = style;
|
|
27
27
|
|
|
28
28
|
try {
|