@mapbox/mapbox-gl-style-spec
Version:
a specification for mapbox gl styles
128 lines (115 loc) • 5.74 kB
text/typescript
import validate from './validate';
import {default as ValidationError, ValidationWarning} from '../error/validation_error';
import getType from '../util/get_type';
import {isFunction} from '../function/index';
import {unbundle, deepUnbundle} from '../util/unbundle_jsonlint';
import {supportsLightExpression, supportsPropertyExpression, supportsZoomExpression} from '../util/properties';
import {isGlobalPropertyConstant, isFeatureConstant, isStateConstant} from '../expression/is_constant';
import {createPropertyExpression, isExpression} from '../expression/index';
import type {ValidationOptions} from './validate';
export type PropertyValidationOptions = ValidationOptions & {
objectKey: string;
layerType: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
layer: any;
};
export default function validateProperty(options: PropertyValidationOptions, propertyType: string): Array<ValidationError> {
const key = options.key;
const style = options.style;
const layer = options.layer;
const styleSpec = options.styleSpec;
const value = options.value;
const propertyKey = options.objectKey;
const layerSpec = styleSpec[`${propertyType}_${options.layerType}`];
if (!layerSpec) return [];
const useThemeMatch = propertyKey.match(/^(.*)-use-theme$/);
if (propertyType === 'paint' && useThemeMatch && layerSpec[useThemeMatch[1]]) {
if (isExpression(value)) {
const errors = [];
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return errors.concat(validate({
key: options.key,
value,
valueSpec: {
"type": "string",
"expression": {
"interpolated": false,
"parameters": [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
style,
styleSpec,
// @ts-expect-error - TS2353 - Object literal may only specify known properties, and 'expressionContext' does not exist in type 'ValidationOptions'.
expressionContext: 'property',
propertyType,
propertyKey
}));
}
return validate({
key,
value,
valueSpec: {type: 'string'},
style,
styleSpec
});
}
const transitionMatch = propertyKey.match(/^(.*)-transition$/);
if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]] && layerSpec[transitionMatch[1]].transition) {
return validate({
key,
value,
valueSpec: styleSpec.transition,
style,
styleSpec
});
}
const valueSpec = options.valueSpec || layerSpec[propertyKey];
if (!valueSpec) {
return [new ValidationWarning(key, value, `unknown property "${propertyKey}"`)];
}
let tokenMatch: RegExpExecArray | undefined;
if (getType(value) === 'string' && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = /^{([^}]+)}$/.exec(value))) {
const example = `\`{ "type": "identity", "property": ${tokenMatch ? JSON.stringify(tokenMatch[1]) : '"_"'} }\``;
return [new ValidationError(
key, value,
`"${propertyKey}" does not support interpolation syntax\n` +
`Use an identity property function instead: ${example}.`)];
}
const errors = [];
if (options.layerType === 'symbol') {
if (propertyKey === 'text-field' && style && !style.glyphs && !style.imports) {
errors.push(new ValidationError(key, value, 'use of "text-field" requires a style "glyphs" property'));
}
if (propertyKey === 'text-font' && isFunction(deepUnbundle(value)) && unbundle(value.type) === 'identity') {
errors.push(new ValidationError(key, value, '"text-font" does not support identity functions'));
}
} else if (options.layerType === 'model' && propertyType === 'paint' && layer && layer.layout && layer.layout.hasOwnProperty('model-id')) {
if (supportsPropertyExpression(valueSpec) && (supportsLightExpression(valueSpec) || supportsZoomExpression(valueSpec))) {
// Performance related style spec limitation: zoom and light expressions are not allowed for e.g. trees.
const expression = createPropertyExpression(deepUnbundle(value), valueSpec);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const expressionObj = (expression.value as any).expression || (expression.value as any)._styleExpression.expression;
if (expressionObj && !isGlobalPropertyConstant(expressionObj, ['measure-light'])) {
if (propertyKey !== 'model-emissive-strength' || (!isFeatureConstant(expressionObj) || !isStateConstant(expressionObj))) {
errors.push(new ValidationError(key, value, `${propertyKey} does not support measure-light expressions when the model layer source is vector tile or GeoJSON.`));
}
}
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return errors.concat(validate({
key: options.key,
value,
valueSpec,
style,
styleSpec,
// @ts-expect-error - TS2353 - Object literal may only specify known properties, and 'expressionContext' does not exist in type 'ValidationOptions'.
expressionContext: 'property',
propertyType,
propertyKey
}));
}