diff --git a/.oxfmtrc.jsonc b/.oxfmtrc.jsonc new file mode 100644 index 000000000..3d97e3268 --- /dev/null +++ b/.oxfmtrc.jsonc @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "tabWidth": 4, + "singleQuote": true, + "bracketSpacing": false, + "trailingComma": "none" +} diff --git a/bin/gl-style-migrate.ts b/bin/gl-style-migrate.ts index 2b4fa65f4..48c2eabc9 100644 --- a/bin/gl-style-migrate.ts +++ b/bin/gl-style-migrate.ts @@ -16,4 +16,3 @@ function help() { console.log('usage:'); console.log(' gl-style-migrate style-v7.json > style-v8.json'); } - diff --git a/bin/gl-style-validate.ts b/bin/gl-style-validate.ts index 1ca81a123..1f71d610b 100644 --- a/bin/gl-style-validate.ts +++ b/bin/gl-style-validate.ts @@ -5,7 +5,7 @@ import rw from 'rw'; import {validateStyle as validate} from '../src/validate_style'; const argv = minimist(process.argv.slice(2), { - boolean: 'json', + boolean: 'json' }); if (argv.help || argv.h || (!argv._.length && process.stdin.isTTY)) { diff --git a/build/bump-version-changelog.js b/build/bump-version-changelog.js index df01835ac..1269e9261 100755 --- a/build/bump-version-changelog.js +++ b/build/bump-version-changelog.js @@ -13,7 +13,8 @@ const changelogPath = 'CHANGELOG.md'; let changelog = readFileSync(changelogPath, 'utf8'); changelog = changelog.replace('## main', `## ${process.argv[2]}`); changelog = changelog.replaceAll('- _...Add new stuff here..._\n', ''); -changelog = `## main +changelog = + `## main ### ✨ Features and improvements - _...Add new stuff here..._ diff --git a/build/generate-docs.ts b/build/generate-docs.ts index 98e88be42..900038cf7 100644 --- a/build/generate-docs.ts +++ b/build/generate-docs.ts @@ -1,4 +1,4 @@ -import v8 from '../src/reference/v8.json' with { type: 'json' }; +import v8 from '../src/reference/v8.json' with {type: 'json'}; import fs from 'fs'; import {formatJSON} from './util'; @@ -15,16 +15,16 @@ type JsonExpressionSyntax = { 'output-type': string | string[]; }[]; parameters?: Parameter[]; -} +}; -type Parameter ={ +type Parameter = { name: string; type: ParameterType; doc?: string; }; // either a basic type, a union of a few basic types or an object -type ParameterType = string | string[] | { [key: string]: JsonObject }; +type ParameterType = string | string[] | {[key: string]: JsonObject}; type JsonSdkSupport = { [info: string]: { @@ -32,7 +32,7 @@ type JsonSdkSupport = { android?: string; ios?: string; }; -} +}; type JsonObject = { required?: boolean; @@ -42,15 +42,15 @@ type JsonObject = { doc: string; requires?: any[]; example: string | object | number | boolean; - expression?: { interpolated?: boolean; parameters?: string[]}; + expression?: {interpolated?: boolean; parameters?: string[]}; transition?: boolean; // for enum type: what is the type of the emum elements - values?: {[key: string]: { doc: string; 'sdk-support'?: JsonSdkSupport }} | number[]; + values?: {[key: string]: {doc: string; 'sdk-support'?: JsonSdkSupport}} | number[]; // for array type: what is the type of the array elements? value?: string; minimum?: number; maximum?: number; -} +}; /** * Capitalizes the first letter of the word. @@ -69,7 +69,8 @@ function capitalize(word: string) { * @returns true if the element should be a topic, false otherwise */ function topicElement(key: string, value: JsonObject): boolean { - return value.type !== 'number' && + return ( + value.type !== 'number' && value.type !== 'boolean' && key !== 'center' && value.type !== '*' && @@ -77,7 +78,8 @@ function topicElement(key: string, value: JsonObject): boolean { key !== 'name' && key !== 'sprite' && key !== 'layers' && - key !== 'sources'; + key !== 'sources' + ); } /** @@ -107,7 +109,7 @@ function supportCell(support?: string): string { // if the string is an issue link, generate a link to it // there is no support yet but there is a tracking issue const maplibreIssue = /https:\/\/github.com\/maplibre\/[^/]+\/issues\/(\d+)/; - const match = support.match(maplibreIssue); + const match = support.match(maplibreIssue); if (match) return `❌ ([#${match[1]}](${support}))`; return support; } @@ -134,18 +136,17 @@ function sdkSupportToMarkdown(support: JsonSdkSupport): string { * @param input the array or string to be joined * @returns the joined string */ -function parameterTypeToType(input: ParameterType):string{ - if ( typeof input === 'string' ) - return input +function parameterTypeToType(input: ParameterType): string { + if (typeof input === 'string') return input; if (Array.isArray(input)) { - return input.join(' | ') + return input.join(' | '); } const parameters = Object.entries(input) - .map(([key, val])=> { + .map(([key, val]) => { const requiredSuffix = val.required ? '' : '?'; - return `${key}${requiredSuffix}: ${jsonObjectToType(val)}` + return `${key}${requiredSuffix}: ${jsonObjectToType(val)}`; }) - .join(', ') + .join(', '); return `{${parameters}}`; } @@ -155,23 +156,27 @@ function parameterTypeToType(input: ParameterType):string{ * @param val - the JSON object * @returns the type string */ -function jsonObjectToType(val: JsonObject):string{ +function jsonObjectToType(val: JsonObject): string { switch (val.type) { case 'boolean': case 'string': case 'number': case 'color': // basic types -> no conversion needed - return val.type + return val.type; case 'array': return `${val.type}<${parameterTypeToType(val.value)}>`; case 'enum': const values = val.values; if (!values || Array.isArray(values)) - throw new Error(`Enum ${JSON.stringify(val)} has no "values" describing the contained Options in the form of an Object`) - return Object.keys(values).map(s=>`"${s}"`).join(' | '); + throw new Error( + `Enum ${JSON.stringify(val)} has no "values" describing the contained Options in the form of an Object` + ); + return Object.keys(values) + .map((s) => `"${s}"`) + .join(' | '); default: - throw new Error(`Unknown "type" ${val.type} for ${JSON.stringify(val)}`) + throw new Error(`Unknown "type" ${val.type} for ${JSON.stringify(val)}`); } } @@ -190,26 +195,27 @@ function expressionSyntaxToMarkdown(key: string, syntax: JsonExpressionSyntax) { }); markdown += `${codeBlockMarkdown(codeBlockLines.join('\n'), 'js')}\n`; for (const parameter of syntax.parameters ?? []) { - const type = parameterTypeToType(parameter.type).replaceAll('<', '<').replaceAll('>', '>'); + const type = parameterTypeToType(parameter.type) + .replaceAll('<', '<') + .replaceAll('>', '>'); markdown += `- \`${parameter.name}\`: \`${type}\``; if (parameter.doc) { markdown += `- ${parameter.doc}`; } - if (typeof parameter.type !== 'string' && !Array.isArray(parameter.type)){ + if (typeof parameter.type !== 'string' && !Array.isArray(parameter.type)) { // the type is an object type => we can attach more documentation about the contained variables - markdown += ' \nParameters:' - Object.entries(parameter.type).forEach(([key, val])=>{ + markdown += ' \nParameters:'; + Object.entries(parameter.type).forEach(([key, val]) => { const type = jsonObjectToType(val).replaceAll('<', '<').replaceAll('>', '>'); - markdown += `\n - \`${key}\`: \`${type}\` - ${val.doc}` - if (val.type==='enum' && val.values){ - markdown += ' \n Possible values are:' + markdown += `\n - \`${key}\`: \`${type}\` - ${val.doc}`; + if (val.type === 'enum' && val.values) { + markdown += ' \n Possible values are:'; for (const [enumKey, enumValue] of Object.entries(val.values)) { const defaultIndicator = val.default === enumKey ? ' *default*' : ''; - markdown += `\n - \`"${enumKey}"\`${defaultIndicator} - ${enumValue.doc}` + markdown += `\n - \`"${enumKey}"\`${defaultIndicator} - ${enumValue.doc}`; } } - - }) + }); } markdown += '\n'; } @@ -299,7 +305,12 @@ function formatRange(minimum?: number, maximum?: number) { * @param paintLayoutText - the text to be used for the paint/layout property * @returns the markdown string */ -function convertPropertyToMarkdown(key: string, value: JsonObject, keyPrefix = '##', paintLayoutText = '') { +function convertPropertyToMarkdown( + key: string, + value: JsonObject, + keyPrefix = '##', + paintLayoutText = '' +) { let markdown = `${keyPrefix} ${key}\n*`; if (paintLayoutText) { markdown += `[${paintLayoutText}](#${paintLayoutText.toLowerCase()}) property. `; @@ -336,8 +347,9 @@ function convertPropertyToMarkdown(key: string, value: JsonObject, keyPrefix = ' } if (value.expression?.interpolated) { if (value.expression.parameters.includes('feature-state')) { - markdown += 'Supports [feature-state](expressions.md#feature-state) and [interpolate](expressions.md#interpolate) expressions. '; - } else { + markdown += + 'Supports [feature-state](expressions.md#feature-state) and [interpolate](expressions.md#interpolate) expressions. '; + } else { markdown += 'Supports [interpolate](expressions.md#interpolate) expressions. '; } } @@ -406,7 +418,7 @@ function createLayersContent() { content += convertPropertyToMarkdown(key, value as JsonObject, '###'); } - for (const layoutKey of Object.keys(v8).filter(key => key.startsWith('layout_'))) { + for (const layoutKey of Object.keys(v8).filter((key) => key.startsWith('layout_'))) { const layerName = layoutKey.replace('layout_', ''); content += `## ${capitalize(layerName)}\n\n`; for (const [key, value] of Object.entries(v8[layoutKey])) { @@ -429,10 +441,8 @@ function createSourcesContent() { doc: 'A vector tile source. Tiles must be in [Mapbox Vector Tile format](https://github.com/mapbox/vector-tile-spec). All geometric coordinates in vector tiles must be between \`-1 * extent\` and \`(extent * 2) - 1\` inclusive. All layers that use a vector source must specify a [`source-layer`](layers.md#source-layer) value. Note that features are only rendered within their originating tile, which may lead to visual artifacts when large values for width, radius, size or offset are specified. To mitigate rendering issues, either reduce the value of the property causing the artifact or, if you have control over the tile generation process, increase the buffer size to ensure that features are fully rendered within the tile.', example: { 'maplibre-streets': { - 'type': 'vector', - 'tiles': [ - 'http://a.example.com/tiles/{z}/{x}/{y}.pbf' - ], + type: 'vector', + tiles: ['http://a.example.com/tiles/{z}/{x}/{y}.pbf'] } }, 'sdk-support': { @@ -447,11 +457,9 @@ function createSourcesContent() { doc: 'A raster tile source.', example: { 'maplibre-satellite': { - 'type': 'raster', - 'tiles': [ - 'http://a.example.com/tiles/{z}/{x}/{y}.png' - ], - 'tileSize': 256 + type: 'raster', + tiles: ['http://a.example.com/tiles/{z}/{x}/{y}.png'], + tileSize: 256 } }, 'sdk-support': { @@ -466,11 +474,9 @@ function createSourcesContent() { doc: 'A raster DEM source. Only supports [Mapbox Terrain RGB](https://blog.mapbox.com/global-elevation-data-6689f1d0ba65) and Mapzen Terrarium tiles.', example: { 'maplibre-terrain-rgb': { - 'type': 'raster-dem', - 'encoding': 'mapbox', - 'tiles': [ - 'http://a.example.com/dem-tiles/{z}/{x}/{y}.png' - ], + type: 'raster-dem', + encoding: 'mapbox', + tiles: ['http://a.example.com/dem-tiles/{z}/{x}/{y}.png'] } }, 'sdk-support': { @@ -485,22 +491,22 @@ function createSourcesContent() { doc: 'A [GeoJSON](http://geojson.org/) source. Data must be provided via a \`"data"\` property, whose value can be a URL or inline GeoJSON. When using in a browser, the GeoJSON data must be on the same domain as the map or served with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers.', example: { 'geojson-marker': { - 'type': 'geojson', - 'data': { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [12.550343, 55.665957] + type: 'geojson', + data: { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [12.550343, 55.665957] }, - 'properties': { - 'title': 'Somewhere', + properties: { + title: 'Somewhere', 'marker-symbol': 'monument' } } }, 'geojson-lines': { - 'type': 'geojson', - 'data': './lines.geojson' + type: 'geojson', + data: './lines.geojson' } }, 'sdk-support': { @@ -524,10 +530,10 @@ function createSourcesContent() { image: { doc: 'An image source. The `url` value contains the image location. The `coordinates` array contains `[longitude, latitude]` pairs for the image corners listed in clockwise order: top left, top right, bottom right, bottom left.', example: { - 'image': { - 'type': 'image', - 'url': 'https://maplibre.org/maplibre-gl-js/docs/assets/radar.gif', - 'coordinates': [ + image: { + type: 'image', + url: 'https://maplibre.org/maplibre-gl-js/docs/assets/radar.gif', + coordinates: [ [-80.425, 46.437], [-71.516, 46.437], [-71.516, 37.936], @@ -544,15 +550,15 @@ function createSourcesContent() { } }, video: { - doc: 'A video source. The `urls` value is an array. For each URL in the array, a video element [source](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source) will be created. To support the video across browsers, supply URLs in multiple formats.\n\nThe `coordinates` array contains `[longitude, latitude]` pairs for the video corners listed in clockwise order: top left, top right, bottom right, bottom left.\n\nWhen rendered as a [raster layer](layers.md#raster), the layer\'s [`raster-fade-duration`](layers.md#raster-fade-duration) property will cause the video to fade in. This happens when playback is started, paused and resumed, or when the video\'s coordinates are updated. To avoid this behavior, set the layer\'s [`raster-fade-duration`](layers.md#raster-fade-duration) property to `0`.', + doc: "A video source. The `urls` value is an array. For each URL in the array, a video element [source](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source) will be created. To support the video across browsers, supply URLs in multiple formats.\n\nThe `coordinates` array contains `[longitude, latitude]` pairs for the video corners listed in clockwise order: top left, top right, bottom right, bottom left.\n\nWhen rendered as a [raster layer](layers.md#raster), the layer's [`raster-fade-duration`](layers.md#raster-fade-duration) property will cause the video to fade in. This happens when playback is started, paused and resumed, or when the video's coordinates are updated. To avoid this behavior, set the layer's [`raster-fade-duration`](layers.md#raster-fade-duration) property to `0`.", example: { - 'video': { - 'type': 'video', - 'urls': [ + video: { + type: 'video', + urls: [ 'https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4', 'https://static-assets.mapbox.com/mapbox-gl-js/drone.webm' ], - 'coordinates': [ + coordinates: [ [-122.51596391201019, 37.56238816766053], [-122.51467645168304, 37.56410183312965], [-122.51309394836426, 37.563391708549425], diff --git a/build/generate-style-spec.ts b/build/generate-style-spec.ts index 4543cc67f..6bca66a67 100755 --- a/build/generate-style-spec.ts +++ b/build/generate-style-spec.ts @@ -24,11 +24,7 @@ function jsDocComment(property) { if (!lines.length) { return undefined; } - return [ - '/**', - ...lines.map(line => ` * ${line}`), - ' */', - ].join('\n'); + return ['/**', ...lines.map((line) => ` * ${line}`), ' */'].join('\n'); } function jsDocBlock(tag, value) { @@ -40,9 +36,11 @@ ${formatJSON(value)} function unionType(values) { if (Array.isArray(values)) { - return values.map(v => JSON.stringify(v)).join(' | '); + return values.map((v) => JSON.stringify(v)).join(' | '); } else { - return Object.keys(values).map(v => JSON.stringify(v)).join(' | '); + return Object.keys(values) + .map((v) => JSON.stringify(v)) + .join(' | '); } } @@ -60,7 +58,11 @@ function propertyType(property) { case 'enum': return unionType(property.values); case 'array': { - const elementType = propertyType(typeof property.value === 'string' ? {type: property.value, values: property.values} : property.value); + const elementType = propertyType( + typeof property.value === 'string' + ? {type: property.value, values: property.values} + : property.value + ); if (property.length) { return `[${Array(property.length).fill(elementType).join(', ')}]`; } else { @@ -104,18 +106,18 @@ function objectDeclaration(key, properties) { function objectType(properties, indent) { return `{ ${Object.keys(properties) - .filter(k => k !== '*') - .flatMap(k => { + .filter((k) => k !== '*') + .flatMap((k) => { const declarations = [propertyDeclaration(k, properties[k])]; if (properties[k].transition) { declarations.push(transitionPropertyDeclaration(k)); } return declarations; }) - .map(declaration => { + .map((declaration) => { return declaration .split('\n') - .map(line => ` ${indent}${line}`) + .map((line) => ` ${indent}${line}`) .join('\n'); }) .join(',\n')} @@ -123,13 +125,18 @@ ${indent}}`; } function sourceTypeName(key) { - return key.replace(/source_(.)(.*)/, (_, _1, _2) => `${_1.toUpperCase()}${_2}SourceSpecification`) + return key + .replace(/source_(.)(.*)/, (_, _1, _2) => `${_1.toUpperCase()}${_2}SourceSpecification`) .replace(/_dem/, 'DEM') .replace(/Geojson/, 'GeoJSON'); } function layerTypeName(key) { - return key.split('-').map(k => k.replace(/(.)(.*)/, (_, _1, _2) => `${_1.toUpperCase()}${_2}`)).concat('LayerSpecification').join(''); + return key + .split('-') + .map((k) => k.replace(/(.)(.*)/, (_, _1, _2) => `${_1.toUpperCase()}${_2}`)) + .concat('LayerSpecification') + .join(''); } function layerType(key) { @@ -165,9 +172,9 @@ function layerType(key) { const layerTypes = Object.keys(spec.layer.type.values); -writeFileSync('src/types.g.ts', +writeFileSync( + 'src/types.g.ts', `// Generated code; do not edit. Edit build/generate-style-spec.ts instead. -/* eslint-disable */ export type ColorSpecification = string; @@ -394,21 +401,24 @@ ${objectDeclaration('ProjectionSpecification', spec.projection)} ${objectDeclaration('TerrainSpecification', spec.terrain)} -${spec.source.map(key => { - let str = objectDeclaration(sourceTypeName(key), spec[key]); - if (sourceTypeName(key) === 'GeoJSONSourceSpecification') { - // This is done in order to overcome the type system's inability to express this type: - str = str.replace(/unknown/, 'GeoJSON.GeoJSON | string'); - } - return str; -}).join('\n\n')} +${spec.source + .map((key) => { + let str = objectDeclaration(sourceTypeName(key), spec[key]); + if (sourceTypeName(key) === 'GeoJSONSourceSpecification') { + // This is done in order to overcome the type system's inability to express this type: + str = str.replace(/unknown/, 'GeoJSON.GeoJSON | string'); + } + return str; + }) + .join('\n\n')} export type SourceSpecification = -${spec.source.map(key => ` | ${sourceTypeName(key)}`).join('\n')} +${spec.source.map((key) => ` | ${sourceTypeName(key)}`).join('\n')} -${layerTypes.map(key => layerType(key)).join('\n\n')} +${layerTypes.map((key) => layerType(key)).join('\n\n')} export type LayerSpecification = -${layerTypes.map(key => ` | ${layerTypeName(key)}`).join('\n')}; +${layerTypes.map((key) => ` | ${layerTypeName(key)}`).join('\n')}; -`); +` +); diff --git a/build/release-notes.js b/build/release-notes.js index 89c1df6f4..12b69d80c 100755 --- a/build/release-notes.js +++ b/build/release-notes.js @@ -20,18 +20,17 @@ const regex = /^## (\d+\.\d+\.\d+.*?)\n(.+?)(?=\n^## \d+\.\d+\.\d+.*?\n)/gms; let releaseNotes = []; let match; - -while (match = regex.exec(changelog)) { + +while ((match = regex.exec(changelog))) { releaseNotes.push({ - 'version': match[1], - 'changelog': match[2].trim(), + version: match[1], + changelog: match[2].trim() }); } const latest = releaseNotes[0]; const previous = releaseNotes[1]; - // Print the release notes template. const templatedReleaseNotes = `https://github.com/maplibre/maplibre-gl-style-spec @@ -41,5 +40,4 @@ ${latest.changelog} ${semver.prerelease(latest.version) ? 'Pre-release version' : ''}`; - -process.stdout.write(templatedReleaseNotes.trimEnd()); \ No newline at end of file +process.stdout.write(templatedReleaseNotes.trimEnd()); diff --git a/build/rollup_plugin_minify_style_spec.ts b/build/rollup_plugin_minify_style_spec.ts index f8c3547a7..ad320dfd4 100644 --- a/build/rollup_plugin_minify_style_spec.ts +++ b/build/rollup_plugin_minify_style_spec.ts @@ -1,7 +1,7 @@ import {Plugin} from 'rollup'; function replacer(key: string, value: any) { - return (key === 'doc' || key === 'example' || key === 'sdk-support') ? undefined : value; + return key === 'doc' || key === 'example' || key === 'sdk-support' ? undefined : value; } export default function minifyStyleSpec(): Plugin { diff --git a/build/util.ts b/build/util.ts index eea287852..b8440fe4f 100644 --- a/build/util.ts +++ b/build/util.ts @@ -2,7 +2,7 @@ import jsonStringify from 'json-stringify-pretty-compact'; /** * Formats a JSON value into a reasonably compact and readable JSON string. - * + * * @param obj - object to be formatted * @returns formatted JSON */ diff --git a/eslint.config.js b/eslint.config.js index bb65fab4d..cb753367b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,132 +1,124 @@ -import typescriptEslint from "@typescript-eslint/eslint-plugin"; -import stylistic from "@stylistic/eslint-plugin"; -import jsdoc from "eslint-plugin-jsdoc"; -import vitest from "@vitest/eslint-plugin"; -import globals from "globals"; -import tsParser from "@typescript-eslint/parser"; - - -export default [{ - ignores: ["build/*.js", "**/dist/*"], -}, { - files: ["**/*.ts"], - plugins: { - "@typescript-eslint": typescriptEslint, - "@stylistic": stylistic, - jsdoc, - vitest, +import typescriptEslint from '@typescript-eslint/eslint-plugin'; +import jsdoc from 'eslint-plugin-jsdoc'; +import vitest from '@vitest/eslint-plugin'; +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; + +export default [ + { + ignores: ['build/*.js', '**/dist/*'] }, - - linterOptions: { - reportUnusedDisableDirectives: true, - }, - - languageOptions: { - globals: { - ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, "off"])), - performance: true, + { + files: ['**/*.ts'], + plugins: { + '@typescript-eslint': typescriptEslint, + jsdoc, + vitest }, - parser: tsParser, - ecmaVersion: 5, - sourceType: "module", - - parserOptions: { - createDefaultProgram: true, - }, - }, - - settings: { - jsdoc: { - ignorePrivate: true, + linterOptions: { + reportUnusedDisableDirectives: true }, - }, - - rules: { - "flowtype/require-valid-file-annotation": [0], - "no-dupe-class-members": "off", - "@typescript-eslint/no-dupe-class-members": ["error"], - - "@typescript-eslint/no-unused-vars": ["warn", { - argsIgnorePattern: "^_", - }], - - "@stylistic/member-delimiter-style": ["error"], - "no-useless-constructor": "off", - "@typescript-eslint/no-useless-constructor": ["error"], - "no-undef": "off", - "no-use-before-define": "off", - "implicit-arrow-linebreak": "off", - "arrow-parens": "off", - "arrow-body-style": "off", - "no-confusing-arrow": "off", - "no-control-regex": "off", - "no-invalid-this": "off", - "no-buffer-constructor": "off", - "array-bracket-spacing": "error", - "consistent-return": "off", - "global-require": "off", - "key-spacing": "error", - "no-eq-null": "off", - "no-lonely-if": "off", - "no-new": "off", - - "no-unused-vars": "off", - "no-warning-comments": "error", - "object-curly-spacing": ["error", "never"], - "prefer-arrow-callback": "error", - "prefer-const": ["error", { - destructuring: "all", - }], + languageOptions: { + globals: { + ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, 'off'])), + performance: true + }, - "prefer-template": "error", - quotes: "off", - "@stylistic/quotes": ["error", "single"], - "no-redeclare": "off", - "@typescript-eslint/no-redeclare": ["error"], - "space-before-function-paren": "off", - "template-curly-spacing": "error", - "no-useless-escape": "off", - indent: "off", - "@stylistic/indent": ["error"], + parser: tsParser, + ecmaVersion: 5, + sourceType: 'module', - "no-multiple-empty-lines": ["error", { - max: 1, - }], - - "jsdoc/check-param-names": "warn", - "jsdoc/require-param": "warn", - "jsdoc/require-param-description": "warn", - "jsdoc/require-param-name": "warn", - "jsdoc/require-returns": "warn", - "jsdoc/require-returns-description": "warn", - "jsdoc/check-alignment": "error", - "jsdoc/check-line-alignment": "error", - "vitest/no-commented-out-tests": "error", - "vitest/no-disabled-tests": "warn", - "vitest/no-focused-tests": "error", - "vitest/prefer-to-contain": "warn", - "vitest/prefer-to-have-length": "warn", - "vitest/valid-expect": "error", - "vitest/prefer-to-be": "warn", - "vitest/no-alias-methods": "warn", - "vitest/no-interpolation-in-snapshots": "warn", + parserOptions: { + createDefaultProgram: true + } + }, - "vitest/no-large-snapshots": ["warn", { - maxSize: 50, - inlineMaxSize: 20, - }] - }, -}, { - files: ["test/**"], + settings: { + jsdoc: { + ignorePrivate: true + } + }, - rules: { - "jsdoc/check-param-names": "off", - "jsdoc/require-param": "off", - "jsdoc/require-param-description": "off", - "jsdoc/require-param-name": "off", - "jsdoc/require-returns": "off", - "jsdoc/require-returns-description": "off", + rules: { + 'no-dupe-class-members': 'off', + '@typescript-eslint/no-dupe-class-members': ['error'], + + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_' + } + ], + + 'no-useless-constructor': 'off', + '@typescript-eslint/no-useless-constructor': ['error'], + 'no-undef': 'off', + 'no-use-before-define': 'off', + 'arrow-body-style': 'off', + 'no-control-regex': 'off', + 'no-invalid-this': 'off', + 'no-buffer-constructor': 'off', + 'consistent-return': 'off', + 'global-require': 'off', + 'no-eq-null': 'off', + 'no-lonely-if': 'off', + 'no-new': 'off', + + 'no-unused-vars': 'off', + 'no-warning-comments': 'error', + 'prefer-arrow-callback': 'error', + + 'prefer-const': [ + 'error', + { + destructuring: 'all' + } + ], + + 'prefer-template': 'error', + 'no-redeclare': 'off', + '@typescript-eslint/no-redeclare': ['error'], + 'no-useless-escape': 'off', + + 'jsdoc/check-param-names': 'warn', + 'jsdoc/require-param': 'warn', + 'jsdoc/require-param-description': 'warn', + 'jsdoc/require-param-name': 'warn', + 'jsdoc/require-returns': 'warn', + 'jsdoc/require-returns-description': 'warn', + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-line-alignment': 'error', + 'vitest/no-commented-out-tests': 'error', + 'vitest/no-disabled-tests': 'warn', + 'vitest/no-focused-tests': 'error', + 'vitest/prefer-to-contain': 'warn', + 'vitest/prefer-to-have-length': 'warn', + 'vitest/valid-expect': 'error', + 'vitest/prefer-to-be': 'warn', + 'vitest/no-alias-methods': 'warn', + 'vitest/no-interpolation-in-snapshots': 'warn', + + 'vitest/no-large-snapshots': [ + 'warn', + { + maxSize: 50, + inlineMaxSize: 20 + } + ] + } }, -}]; \ No newline at end of file + { + files: ['test/**'], + + rules: { + 'jsdoc/check-param-names': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-param-name': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-returns-description': 'off' + } + } +]; diff --git a/package-lock.json b/package-lock.json index 558eb3e4b..be82f8a72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,6 @@ "@rollup/plugin-strip": "^3.0.4", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.3.0", - "@stylistic/eslint-plugin": "^5.6.1", "@types/eslint": "^9.6.1", "@types/geojson": "^7946.0.16", "@types/node": "^24.10.1", @@ -44,6 +43,7 @@ "eslint-plugin-jsdoc": "^61.4.1", "glob": "^13.0.0", "globals": "^16.5.0", + "oxfmt": "^0.15.0", "rollup": "^4.53.3", "rollup-plugin-preserve-shebang": "^1.0.1", "semver": "^7.7.3", @@ -62,22 +62,6 @@ "node": ">=0.10.0" } }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.1.7.tgz", - "integrity": "sha512-Ok5fYhtwdyJQmU1PpEv6Si7Y+A4cYb8yNM9oiIJC9TzXPMuN9fvdonKJqcnz9TbFqV6bQ8z0giRq0iaOpGZV2g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -150,131 +134,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz", - "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz", - "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@es-joy/jsdoccomment": { "version": "0.76.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.76.0.tgz", @@ -744,25 +603,6 @@ "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", @@ -838,9 +678,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -850,7 +690,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -861,17 +701,10 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -892,19 +725,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1153,6 +973,118 @@ "node": ">= 8" } }, + "node_modules/@oxfmt/darwin-arm64": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/darwin-arm64/-/darwin-arm64-0.15.0.tgz", + "integrity": "sha512-M5xiXkqtwG/1yVlNZJaXeFJGs1jVTT7Q6gfdIU3nO8wXIj0lsfcirc55XPObWohA8T73oLh3Cp1oSQluxVXQrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxfmt/darwin-x64": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/darwin-x64/-/darwin-x64-0.15.0.tgz", + "integrity": "sha512-HXBZBV1oqmZWcmXXQE+zXNpe8nOXyeY9oLeW6KfflR1HCicR75Tmer6Y5/euVOb/mDfegkkjReRoQnYxJ6CmtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxfmt/linux-arm64-gnu": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-gnu/-/linux-arm64-gnu-0.15.0.tgz", + "integrity": "sha512-udaMrO+a6XFCtr0wPMHNFxyYD824y0FzjMrll77P8t2jDxiNAwbEzbz7SU0VKSEH36K62bgS2Dn/yfWedXQFrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/linux-arm64-musl": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-arm64-musl/-/linux-arm64-musl-0.15.0.tgz", + "integrity": "sha512-HHFSA5bL60DG2QjI9KnkCCImdBitLoun1LGitMpI9SQDYeKezDr8rz4IQWeE2h45dWltFFm5fONh6cwSLFeHOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/linux-x64-gnu": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-gnu/-/linux-x64-gnu-0.15.0.tgz", + "integrity": "sha512-M1dN2Erxd5d5e/YP19Rv6TsDiHAgeAydOX/Qr42/CQfP93edohFOXvZsgthEGQVKNcc45SNDd2Fscx3szw68iA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/linux-x64-musl": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/linux-x64-musl/-/linux-x64-musl-0.15.0.tgz", + "integrity": "sha512-Y7f+Daz9/Hnmtkja6Va0E8iYYSSV4RGyIz8FD2flfd3BIA1TOVOYwlmkAFQkBUhwuIDsYOtsWcAXJAf8cWiAEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxfmt/win32-arm64": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/win32-arm64/-/win32-arm64-0.15.0.tgz", + "integrity": "sha512-IhrYWgy9yF5tynkScyiQEiTxvEXVo8L++JuIIv36p2Vdk7wPNtfq1tHSiu1nrUw33idjsWZ0QsP6Sx2e+48s0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxfmt/win32-x64": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@oxfmt/win32-x64/-/win32-x64-0.15.0.tgz", + "integrity": "sha512-ngMLVe0UxjKtjZ79MYp0qJDolltFOAFp511Tfr89nVv077cQosqZ0Sx7hmIQLA0G+wyADfG/UMNcpPVyC/Msig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -1687,40 +1619,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@stylistic/eslint-plugin": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.6.1.tgz", - "integrity": "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.0", - "@typescript-eslint/types": "^8.47.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": ">=9.0.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1839,28 +1737,18 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", - "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz", - "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz", + "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", - "debug": "^4.3.4" + "@typescript-eslint/utils": "8.47.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1874,16 +1762,17 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/project-service": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", - "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", + "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.47.0", - "@typescript-eslint/types": "^8.47.0", - "debug": "^4.3.4" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.47.0", + "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/typescript-estree": "8.47.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1893,18 +1782,64 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", - "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz", + "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", "dependencies": { + "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0" + "@typescript-eslint/typescript-estree": "8.47.0", + "@typescript-eslint/visitor-keys": "8.47.0", + "debug": "^4.3.4" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1912,14 +1847,23 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { + "node_modules/@typescript-eslint/project-service": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", - "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", + "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.47.0", + "@typescript-eslint/types": "^8.47.0", + "debug": "^4.3.4" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1931,19 +1875,30 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { + "node_modules/@typescript-eslint/scope-manager": { "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz", - "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", + "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/utils": "8.47.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/visitor-keys": "8.47.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", + "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", + "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1952,7 +1907,6 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -1999,30 +1953,6 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", - "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz", @@ -2041,19 +1971,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@vitest/coverage-v8": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.13.tgz", @@ -2138,6 +2055,179 @@ } } }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/project-service": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", + "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.48.0", + "@typescript-eslint/types": "^8.48.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", + "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", + "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", + "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.48.0", + "@typescript-eslint/tsconfig-utils": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", + "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", + "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.48.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/expect": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.13.tgz", @@ -2312,18 +2402,6 @@ "node": ">=0.4.0" } }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2380,6 +2458,13 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2452,6 +2537,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2540,7 +2626,8 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/create-require": { "version": "1.1.1", @@ -2562,38 +2649,6 @@ "node": ">= 8" } }, - "node_modules/cssstyle": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", - "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@asamuzakjp/css-color": "^3.1.2", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2612,15 +2667,6 @@ } } }, - "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2667,21 +2713,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -2859,10 +2890,43 @@ } }, "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2871,38 +2935,26 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/find-up": { @@ -2941,6 +2993,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2981,19 +3034,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -3094,7 +3134,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -3301,21 +3342,6 @@ "node": ">= 0.4" } }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -3340,57 +3366,12 @@ "dev": true, "license": "MIT" }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -3412,16 +3393,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3492,14 +3463,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -3561,6 +3524,19 @@ "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdoc-type-pratt-parser": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-6.10.0.tgz", @@ -3571,48 +3547,6 @@ "node": ">=20.0.0" } }, - "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3663,16 +3597,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/magic-string": { "version": "0.30.21", @@ -3830,15 +3756,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/object-deep-merge": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", @@ -3863,6 +3780,32 @@ "node": ">= 0.8.0" } }, + "node_modules/oxfmt": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.15.0.tgz", + "integrity": "sha512-WOPBsZteu/H7t+3UGuEDyYi87+v2H96zyBwrEIwq4SEAt8P3bL6velrvs71V6aOpxjLcIPAnKjXeHScvFImI4w==", + "dev": true, + "license": "MIT", + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxfmt/darwin-arm64": "0.15.0", + "@oxfmt/darwin-x64": "0.15.0", + "@oxfmt/linux-arm64-gnu": "0.15.0", + "@oxfmt/linux-arm64-musl": "0.15.0", + "@oxfmt/linux-x64-gnu": "0.15.0", + "@oxfmt/linux-x64-musl": "0.15.0", + "@oxfmt/win32-arm64": "0.15.0", + "@oxfmt/win32-x64": "0.15.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3908,21 +3851,6 @@ "dev": true, "license": "MIT" }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4121,6 +4049,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -4192,15 +4130,6 @@ "sourcemap-codec": "^1.4.8" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4250,28 +4179,6 @@ } ] }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -4447,6 +4354,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -4478,14 +4386,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/terser": { "version": "5.17.7", "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", @@ -4550,30 +4450,6 @@ "node": ">=14.0.0" } }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4614,36 +4490,6 @@ "node": ">=6" } }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -4912,76 +4758,6 @@ } } }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5031,50 +4807,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index c30fcc5c8..1d197e83c 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "compile": "tsc", "lint": "eslint", "typecheck": "tsc --noEmit", - "prepare": "npm run generate-style-spec" + "prepare": "npm run generate-style-spec", + "fmt": "oxfmt" }, "repository": { "type": "git", @@ -62,6 +63,7 @@ }, "sideEffects": false, "devDependencies": { + "oxfmt": "^0.15.0", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", @@ -69,7 +71,6 @@ "@rollup/plugin-strip": "^3.0.4", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.3.0", - "@stylistic/eslint-plugin": "^5.6.1", "@types/eslint": "^9.6.1", "@types/geojson": "^7946.0.16", "@types/node": "^24.10.1", diff --git a/rollup.config.ts b/rollup.config.ts index 3ce101c46..e5f0cf01e 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -26,76 +26,86 @@ const rollupPlugins = [ commonjs() ]; -const config: RollupOptions[] = [{ - input: './src/index.ts', - output: [{ - file: 'dist/index.mjs', - format: 'es', - sourcemap: true - }, +const config: RollupOptions[] = [ { - name: 'maplibreGlStyleSpecification', - file: 'dist/index.cjs', - format: 'umd', - sourcemap: true, - globals: { - fs: 'fs' - } - }], - plugins: rollupPlugins -}, -{ - input: './bin/gl-style-format.ts', - output: [{ - file: 'dist/gl-style-format.mjs', - format: 'es', - sourcemap: true + input: './src/index.ts', + output: [ + { + file: 'dist/index.mjs', + format: 'es', + sourcemap: true + }, + { + name: 'maplibreGlStyleSpecification', + file: 'dist/index.cjs', + format: 'umd', + sourcemap: true, + globals: { + fs: 'fs' + } + } + ], + plugins: rollupPlugins }, { - name: 'maplibreGlStyleSpecification', - file: 'dist/gl-style-format.cjs', - format: 'umd', - sourcemap: true, - globals: { - fs: 'fs' - } - }], - plugins: [...rollupPlugins, shebang()] -}, -{ - input: './bin/gl-style-migrate.ts', - output: [{ - file: 'dist/gl-style-migrate.mjs', - format: 'es', - sourcemap: true + input: './bin/gl-style-format.ts', + output: [ + { + file: 'dist/gl-style-format.mjs', + format: 'es', + sourcemap: true + }, + { + name: 'maplibreGlStyleSpecification', + file: 'dist/gl-style-format.cjs', + format: 'umd', + sourcemap: true, + globals: { + fs: 'fs' + } + } + ], + plugins: [...rollupPlugins, shebang()] }, { - name: 'maplibreGlStyleSpecification', - file: 'dist/gl-style-migrate.cjs', - format: 'umd', - sourcemap: true, - globals: { - fs: 'fs' - } - }], - plugins: [...rollupPlugins, shebang()] -}, -{ - input: './bin/gl-style-validate.ts', - output: [{ - file: 'dist/gl-style-validate.mjs', - format: 'es', - sourcemap: true + input: './bin/gl-style-migrate.ts', + output: [ + { + file: 'dist/gl-style-migrate.mjs', + format: 'es', + sourcemap: true + }, + { + name: 'maplibreGlStyleSpecification', + file: 'dist/gl-style-migrate.cjs', + format: 'umd', + sourcemap: true, + globals: { + fs: 'fs' + } + } + ], + plugins: [...rollupPlugins, shebang()] }, { - name: 'maplibreGlStyleSpecification', - file: 'dist/gl-style-validate.cjs', - format: 'umd', - sourcemap: true, - globals: { - fs: 'fs' - } - }], - plugins: [...rollupPlugins, shebang()] -}]; + input: './bin/gl-style-validate.ts', + output: [ + { + file: 'dist/gl-style-validate.mjs', + format: 'es', + sourcemap: true + }, + { + name: 'maplibreGlStyleSpecification', + file: 'dist/gl-style-validate.cjs', + format: 'umd', + sourcemap: true, + globals: { + fs: 'fs' + } + } + ], + plugins: [...rollupPlugins, shebang()] + } +]; export default config; diff --git a/src/deref.test.ts b/src/deref.test.ts index c9df0df80..eceb88490 100644 --- a/src/deref.test.ts +++ b/src/deref.test.ts @@ -3,45 +3,49 @@ import {describe, test, expect} from 'vitest'; describe('deref', () => { test('derefs a ref layer which follows its parent', () => { - expect(derefLayers([ - { - 'id': 'parent', - 'type': 'line' - } as LayerWithRef, - { - 'id': 'child', - 'ref': 'parent' - } as LayerWithRef - ])).toEqual([ - { - 'id': 'parent', - 'type': 'line' + expect( + derefLayers([ + { + id: 'parent', + type: 'line' + } as LayerWithRef, + { + id: 'child', + ref: 'parent' + } as LayerWithRef + ]) + ).toEqual([ + { + id: 'parent', + type: 'line' }, { - 'id': 'child', - 'type': 'line' + id: 'child', + type: 'line' } ]); }); test('derefs a ref layer which precedes its parent', () => { - expect(derefLayers([ - { - 'id': 'child', - 'ref': 'parent' - } as LayerWithRef, - { - 'id': 'parent', - 'type': 'line' - } as LayerWithRef - ])).toEqual([ - { - 'id': 'child', - 'type': 'line' + expect( + derefLayers([ + { + id: 'child', + ref: 'parent' + } as LayerWithRef, + { + id: 'parent', + type: 'line' + } as LayerWithRef + ]) + ).toEqual([ + { + id: 'child', + type: 'line' }, { - 'id': 'parent', - 'type': 'line' + id: 'parent', + type: 'line' } ]); }); diff --git a/src/deref.ts b/src/deref.ts index 13b176446..911ac7678 100644 --- a/src/deref.ts +++ b/src/deref.ts @@ -1,8 +1,7 @@ - import {refProperties} from './util/ref_properties'; import {LayerSpecification} from './types.g'; -export type LayerWithRef = LayerSpecification & { ref?: string }; +export type LayerWithRef = LayerSpecification & {ref?: string}; function deref(layer: LayerWithRef, parent: LayerSpecification): LayerSpecification { const result: Partial = {}; diff --git a/src/diff.test.ts b/src/diff.test.ts index 849b97193..8b19dc400 100644 --- a/src/diff.test.ts +++ b/src/diff.test.ts @@ -4,484 +4,651 @@ import {describe, test, expect} from 'vitest'; describe('diff', () => { test('layers id equal', () => { - expect(diff({ - layers: [{id: 'a'}] - } as StyleSpecification, { - layers: [{id: 'a'}] - } as StyleSpecification)).toEqual([]); + expect( + diff( + { + layers: [{id: 'a'}] + } as StyleSpecification, + { + layers: [{id: 'a'}] + } as StyleSpecification + ) + ).toEqual([]); }); test('version not equal', () => { - expect(diff({ - version: 7, - layers: [{id: 'a'}] - } as any as StyleSpecification, { - version: 8, - layers: [{id: 'a'}] - } as StyleSpecification)).toEqual([ - {command: 'setStyle', args: [{version: 8, layers: [{id: 'a'}]}]} - ]); + expect( + diff( + { + version: 7, + layers: [{id: 'a'}] + } as any as StyleSpecification, + { + version: 8, + layers: [{id: 'a'}] + } as StyleSpecification + ) + ).toEqual([{command: 'setStyle', args: [{version: 8, layers: [{id: 'a'}]}]}]); }); test('add layer at the end', () => { - expect(diff({ - layers: [{id: 'a'}] - } as StyleSpecification, { - layers: [{id: 'a'}, {id: 'b'}] - } as StyleSpecification)).toEqual([ - {command: 'addLayer', args: [{id: 'b'}, undefined]} - ]); + expect( + diff( + { + layers: [{id: 'a'}] + } as StyleSpecification, + { + layers: [{id: 'a'}, {id: 'b'}] + } as StyleSpecification + ) + ).toEqual([{command: 'addLayer', args: [{id: 'b'}, undefined]}]); }); test('add layer at the beginning', () => { - expect(diff({ - layers: [{id: 'b'}] - } as StyleSpecification, { - layers: [{id: 'a'}, {id: 'b'}] - } as StyleSpecification)).toEqual([ - {command: 'addLayer', args: [{id: 'a'}, 'b']} - ]); + expect( + diff( + { + layers: [{id: 'b'}] + } as StyleSpecification, + { + layers: [{id: 'a'}, {id: 'b'}] + } as StyleSpecification + ) + ).toEqual([{command: 'addLayer', args: [{id: 'a'}, 'b']}]); }); test('remove layer', () => { - expect(diff({ - layers: [{id: 'a'}, {id: 'b', source: 'foo', nested: [1]}] - } as StyleSpecification, { - layers: [{id: 'a'}] - } as StyleSpecification)).toEqual([ - {command: 'removeLayer', args: ['b']} - ]); + expect( + diff( + { + layers: [{id: 'a'}, {id: 'b', source: 'foo', nested: [1]}] + } as StyleSpecification, + { + layers: [{id: 'a'}] + } as StyleSpecification + ) + ).toEqual([{command: 'removeLayer', args: ['b']}]); }); test('remove and add layer', () => { - expect(diff({ - layers: [{id: 'a'}, {id: 'b'}] - } as StyleSpecification, { - layers: [{id: 'b'}, {id: 'a'}] - } as StyleSpecification)).toEqual([ + expect( + diff( + { + layers: [{id: 'a'}, {id: 'b'}] + } as StyleSpecification, + { + layers: [{id: 'b'}, {id: 'a'}] + } as StyleSpecification + ) + ).toEqual([ {command: 'removeLayer', args: ['a']}, {command: 'addLayer', args: [{id: 'a'}, undefined]} ]); }); test('set paint property', () => { - expect(diff({ - layers: [{id: 'a', paint: {foo: 1}}] - } as any as StyleSpecification, { - layers: [{id: 'a', paint: {foo: 2}}] - } as any as StyleSpecification)).toEqual([ - {command: 'setPaintProperty', args: ['a', 'foo', 2, null]} - ]); + expect( + diff( + { + layers: [{id: 'a', paint: {foo: 1}}] + } as any as StyleSpecification, + { + layers: [{id: 'a', paint: {foo: 2}}] + } as any as StyleSpecification + ) + ).toEqual([{command: 'setPaintProperty', args: ['a', 'foo', 2, null]}]); }); test('set paint property with light', () => { - expect(diff({ - layers: [{id: 'a', 'paint.light': {foo: 1}}] - } as any as StyleSpecification, { - layers: [{id: 'a', 'paint.light': {foo: 2}}] - } as any as StyleSpecification)).toEqual([ - {command: 'setPaintProperty', args: ['a', 'foo', 2, 'light']} - ]); + expect( + diff( + { + layers: [{id: 'a', 'paint.light': {foo: 1}}] + } as any as StyleSpecification, + { + layers: [{id: 'a', 'paint.light': {foo: 2}}] + } as any as StyleSpecification + ) + ).toEqual([{command: 'setPaintProperty', args: ['a', 'foo', 2, 'light']}]); }); test('set paint property with ramp', () => { - expect(diff({ - layers: [{id: 'a', paint: {foo: {ramp: [1, 2]}}}] - } as any as StyleSpecification, { - layers: [{id: 'a', paint: {foo: {ramp: [1]}}}] - } as any as StyleSpecification)).toEqual([ - {command: 'setPaintProperty', args: ['a', 'foo', {ramp: [1]}, null]} - ]); + expect( + diff( + { + layers: [{id: 'a', paint: {foo: {ramp: [1, 2]}}}] + } as any as StyleSpecification, + { + layers: [{id: 'a', paint: {foo: {ramp: [1]}}}] + } as any as StyleSpecification + ) + ).toEqual([{command: 'setPaintProperty', args: ['a', 'foo', {ramp: [1]}, null]}]); }); test('set layout property', () => { - expect(diff({ - layers: [{id: 'a', layout: {foo: 1}}] - } as any as StyleSpecification, { - layers: [{id: 'a', layout: {foo: 2}}] - } as any as StyleSpecification)).toEqual([ - {command: 'setLayoutProperty', args: ['a', 'foo', 2, null]} - ]); + expect( + diff( + { + layers: [{id: 'a', layout: {foo: 1}}] + } as any as StyleSpecification, + { + layers: [{id: 'a', layout: {foo: 2}}] + } as any as StyleSpecification + ) + ).toEqual([{command: 'setLayoutProperty', args: ['a', 'foo', 2, null]}]); }); test('set filter', () => { - expect(diff({ - layers: [{id: 'a', filter: ['==', 'foo', 'bar']}] - } as StyleSpecification, { - layers: [{id: 'a', filter: ['==', 'foo', 'baz']}] - } as StyleSpecification)).toEqual([ - {command: 'setFilter', args: ['a', ['==', 'foo', 'baz']]} - ]); + expect( + diff( + { + layers: [{id: 'a', filter: ['==', 'foo', 'bar']}] + } as StyleSpecification, + { + layers: [{id: 'a', filter: ['==', 'foo', 'baz']}] + } as StyleSpecification + ) + ).toEqual([{command: 'setFilter', args: ['a', ['==', 'foo', 'baz']]}]); }); test('remove source', () => { - expect(diff({ - sources: {foo: 1} - } as any as StyleSpecification, { - sources: {} - } as StyleSpecification)).toEqual([ - {command: 'removeSource', args: ['foo']} - ]); + expect( + diff( + { + sources: {foo: 1} + } as any as StyleSpecification, + { + sources: {} + } as StyleSpecification + ) + ).toEqual([{command: 'removeSource', args: ['foo']}]); }); test('add source', () => { - expect(diff({ - sources: {} - } as StyleSpecification, { - sources: {foo: 1} - } as any as StyleSpecification)).toEqual([ - {command: 'addSource', args: ['foo', 1]} - ]); + expect( + diff( + { + sources: {} + } as StyleSpecification, + { + sources: {foo: 1} + } as any as StyleSpecification + ) + ).toEqual([{command: 'addSource', args: ['foo', 1]}]); }); test('set goejson source data', () => { - expect(diff({ - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []} - } - } - } as any as StyleSpecification, { - sources: { - foo: { - type: 'geojson', - data: { + expect( + diff( + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []} + } + } + } as any as StyleSpecification, + { + sources: { + foo: { + type: 'geojson', + data: { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + geometry: {type: 'Point', coordinates: [10, 20]} + } + ] + } + } + } + } as any as StyleSpecification + ) + ).toEqual([ + { + command: 'setGeoJSONSourceData', + args: [ + 'foo', + { type: 'FeatureCollection', - features: [{ - type: 'Feature', - geometry: {type: 'Point', coordinates: [10, 20]} - }] + features: [ + { + type: 'Feature', + geometry: {type: 'Point', coordinates: [10, 20]} + } + ] } - } + ] } - } as any as StyleSpecification)).toEqual([ - {command: 'setGeoJSONSourceData', args: ['foo', { - type: 'FeatureCollection', - features: [{ - type: 'Feature', - geometry: {type: 'Point', coordinates: [10, 20]} - }] - }]} ]); }); test('remove and add source', () => { - expect(diff({ - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []} - } - } - } as any as StyleSpecification, { - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []}, - cluster: true - } - } - } as any as StyleSpecification)).toEqual([ + expect( + diff( + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []} + } + } + } as any as StyleSpecification, + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []}, + cluster: true + } + } + } as any as StyleSpecification + ) + ).toEqual([ {command: 'removeSource', args: ['foo']}, - {command: 'addSource', args: ['foo', { - type: 'geojson', - cluster: true, - data: {type: 'FeatureCollection', features: []} - }]} + { + command: 'addSource', + args: [ + 'foo', + { + type: 'geojson', + cluster: true, + data: {type: 'FeatureCollection', features: []} + } + ] + } ]); }); test('remove and add source with clusterRadius', () => { - expect(diff({ - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []}, - cluster: true - } - } - } as any as StyleSpecification, { - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []}, - cluster: true, - clusterRadius: 100 - } - } - } as any as StyleSpecification)).toEqual([ + expect( + diff( + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []}, + cluster: true + } + } + } as any as StyleSpecification, + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []}, + cluster: true, + clusterRadius: 100 + } + } + } as any as StyleSpecification + ) + ).toEqual([ {command: 'removeSource', args: ['foo']}, - {command: 'addSource', args: ['foo', { - type: 'geojson', - cluster: true, - clusterRadius: 100, - data: {type: 'FeatureCollection', features: []} - }]} + { + command: 'addSource', + args: [ + 'foo', + { + type: 'geojson', + cluster: true, + clusterRadius: 100, + data: {type: 'FeatureCollection', features: []} + } + ] + } ]); }); test('remove and add source without clusterRadius', () => { - expect(diff({ - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []}, - cluster: true, - clusterRadius: 100 - } - } - } as any as StyleSpecification, { - sources: { - foo: { - type: 'geojson', - data: {type: 'FeatureCollection', features: []}, - cluster: true - } - } - } as any as StyleSpecification)).toEqual([ + expect( + diff( + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []}, + cluster: true, + clusterRadius: 100 + } + } + } as any as StyleSpecification, + { + sources: { + foo: { + type: 'geojson', + data: {type: 'FeatureCollection', features: []}, + cluster: true + } + } + } as any as StyleSpecification + ) + ).toEqual([ {command: 'removeSource', args: ['foo']}, - {command: 'addSource', args: ['foo', { - type: 'geojson', - cluster: true, - data: {type: 'FeatureCollection', features: []} - }]} + { + command: 'addSource', + args: [ + 'foo', + { + type: 'geojson', + cluster: true, + data: {type: 'FeatureCollection', features: []} + } + ] + } ]); }); test('global metadata', () => { - expect(diff({} as StyleSpecification, { - metadata: {'maplibre:author': 'nobody'} - } as StyleSpecification)).toEqual([]); + expect( + diff( + {} as StyleSpecification, + { + metadata: {'maplibre:author': 'nobody'} + } as StyleSpecification + ) + ).toEqual([]); }); test('layer metadata', () => { - expect(diff({ - layers: [{id: 'a', metadata: {'maplibre:group': 'Group Name'}}] - } as StyleSpecification, { - layers: [{id: 'a', metadata: {'maplibre:group': 'Another Name'}}] - } as StyleSpecification)).toEqual([]); + expect( + diff( + { + layers: [{id: 'a', metadata: {'maplibre:group': 'Group Name'}}] + } as StyleSpecification, + { + layers: [{id: 'a', metadata: {'maplibre:group': 'Another Name'}}] + } as StyleSpecification + ) + ).toEqual([]); }); test('set state', () => { - expect(diff({ - state: {foo: 1} - } as any as StyleSpecification, { - state: {foo: 2} - } as any as StyleSpecification)).toEqual([ - {command: 'setGlobalState', args: [{foo: 2}]} - ]); + expect( + diff( + { + state: {foo: 1} + } as any as StyleSpecification, + { + state: {foo: 2} + } as any as StyleSpecification + ) + ).toEqual([{command: 'setGlobalState', args: [{foo: 2}]}]); }); test('set center', () => { - expect(diff({ - center: [0, 0] - } as StyleSpecification, { - center: [1, 1] - } as StyleSpecification)).toEqual([ - {command: 'setCenter', args: [[1, 1]]} - ]); + expect( + diff( + { + center: [0, 0] + } as StyleSpecification, + { + center: [1, 1] + } as StyleSpecification + ) + ).toEqual([{command: 'setCenter', args: [[1, 1]]}]); }); test('set centerAltitude to undefined', () => { - expect(diff({ - centerAltitude: 1 - } as StyleSpecification, { - } as StyleSpecification)).toEqual([ - {command: 'setCenterAltitude', args: [undefined]} - ]); + expect( + diff( + { + centerAltitude: 1 + } as StyleSpecification, + {} as StyleSpecification + ) + ).toEqual([{command: 'setCenterAltitude', args: [undefined]}]); }); test('set centerAltitude', () => { - expect(diff({ - centerAltitude: 0 - } as StyleSpecification, { - centerAltitude: 1 - } as StyleSpecification)).toEqual([ - {command: 'setCenterAltitude', args: [1]} - ]); + expect( + diff( + { + centerAltitude: 0 + } as StyleSpecification, + { + centerAltitude: 1 + } as StyleSpecification + ) + ).toEqual([{command: 'setCenterAltitude', args: [1]}]); }); test('set zoom', () => { - expect(diff({ - zoom: 12 - } as StyleSpecification, { - zoom: 15 - } as StyleSpecification)).toEqual([ - {command: 'setZoom', args: [15]} - ]); + expect( + diff( + { + zoom: 12 + } as StyleSpecification, + { + zoom: 15 + } as StyleSpecification + ) + ).toEqual([{command: 'setZoom', args: [15]}]); }); test('set bearing', () => { - expect(diff({ - bearing: 0 - } as StyleSpecification, { - bearing: 180 - } as StyleSpecification)).toEqual([ - {command: 'setBearing', args: [180]} - ]); + expect( + diff( + { + bearing: 0 + } as StyleSpecification, + { + bearing: 180 + } as StyleSpecification + ) + ).toEqual([{command: 'setBearing', args: [180]}]); }); test('set pitch', () => { - expect(diff({ - pitch: 0 - } as StyleSpecification, { - pitch: 1 - } as StyleSpecification)).toEqual([ - {command: 'setPitch', args: [1]} - ]); + expect( + diff( + { + pitch: 0 + } as StyleSpecification, + { + pitch: 1 + } as StyleSpecification + ) + ).toEqual([{command: 'setPitch', args: [1]}]); }); test('set roll to undefined', () => { - expect(diff({ - roll: 1 - } as StyleSpecification, { - } as StyleSpecification)).toEqual([ - {command: 'setRoll', args: [undefined]} - ]); + expect( + diff( + { + roll: 1 + } as StyleSpecification, + {} as StyleSpecification + ) + ).toEqual([{command: 'setRoll', args: [undefined]}]); }); test('set roll', () => { - expect(diff({ - roll: 0 - } as StyleSpecification, { - roll: 1 - } as StyleSpecification)).toEqual([ - {command: 'setRoll', args: [1]} - ]); + expect( + diff( + { + roll: 0 + } as StyleSpecification, + { + roll: 1 + } as StyleSpecification + ) + ).toEqual([{command: 'setRoll', args: [1]}]); }); test('no changes in light', () => { - expect(diff({ - light: { - anchor: 'map', - color: 'white', - position: [0, 1, 0], - intensity: 1 - } - } as StyleSpecification, { - light: { - anchor: 'map', - color: 'white', - position: [0, 1, 0], - intensity: 1 - } - } as StyleSpecification)).toEqual([ - ]); + expect( + diff( + { + light: { + anchor: 'map', + color: 'white', + position: [0, 1, 0], + intensity: 1 + } + } as StyleSpecification, + { + light: { + anchor: 'map', + color: 'white', + position: [0, 1, 0], + intensity: 1 + } + } as StyleSpecification + ) + ).toEqual([]); }); test('set light anchor', () => { - expect(diff({ - light: {anchor: 'map'} - } as StyleSpecification, { - light: {anchor: 'viewport'} - } as StyleSpecification)).toEqual([ - {command: 'setLight', args: [{'anchor': 'viewport'}]} - ]); + expect( + diff( + { + light: {anchor: 'map'} + } as StyleSpecification, + { + light: {anchor: 'viewport'} + } as StyleSpecification + ) + ).toEqual([{command: 'setLight', args: [{anchor: 'viewport'}]}]); }); test('set light color', () => { - expect(diff({ - light: {color: 'white'} - } as StyleSpecification, { - light: {color: 'red'} - } as StyleSpecification)).toEqual([ - {command: 'setLight', args: [{'color': 'red'}]} - ]); + expect( + diff( + { + light: {color: 'white'} + } as StyleSpecification, + { + light: {color: 'red'} + } as StyleSpecification + ) + ).toEqual([{command: 'setLight', args: [{color: 'red'}]}]); }); test('set light position', () => { - expect(diff({ - light: {position: [0, 1, 0]} - } as StyleSpecification, { - light: {position: [1, 0, 0]} - } as StyleSpecification)).toEqual([ - {command: 'setLight', args: [{'position': [1, 0, 0]}]} - ]); + expect( + diff( + { + light: {position: [0, 1, 0]} + } as StyleSpecification, + { + light: {position: [1, 0, 0]} + } as StyleSpecification + ) + ).toEqual([{command: 'setLight', args: [{position: [1, 0, 0]}]}]); }); test('set light intensity', () => { - expect(diff({ - light: {intensity: 1} - } as StyleSpecification, { - light: {intensity: 10} - } as StyleSpecification)).toEqual([ - {command: 'setLight', args: [{'intensity': 10}]} - ]); + expect( + diff( + { + light: {intensity: 1} + } as StyleSpecification, + { + light: {intensity: 10} + } as StyleSpecification + ) + ).toEqual([{command: 'setLight', args: [{intensity: 10}]}]); }); test('set light anchor and color', () => { - expect(diff({ - light: { - anchor: 'map', - color: 'orange', - position: [2, 80, 30], - intensity: 1.0 - } - } as StyleSpecification, { - light: { - anchor: 'map', - color: 'red', - position: [1, 40, 30], - intensity: 1.0 + expect( + diff( + { + light: { + anchor: 'map', + color: 'orange', + position: [2, 80, 30], + intensity: 1.0 + } + } as StyleSpecification, + { + light: { + anchor: 'map', + color: 'red', + position: [1, 40, 30], + intensity: 1.0 + } + } as StyleSpecification + ) + ).toEqual([ + { + command: 'setLight', + args: [ + { + anchor: 'map', + color: 'red', + position: [1, 40, 30], + intensity: 1.0 + } + ] } - } as StyleSpecification)).toEqual([ - {command: 'setLight', args: [{ - anchor: 'map', - color: 'red', - position: [1, 40, 30], - intensity: 1.0 - }]} ]); }); test('add and remove layer on source change', () => { - expect(diff({ - layers: [{id: 'a', source: 'source-one'}] - } as StyleSpecification, { - layers: [{id: 'a', source: 'source-two'}] - } as StyleSpecification)).toEqual([ + expect( + diff( + { + layers: [{id: 'a', source: 'source-one'}] + } as StyleSpecification, + { + layers: [{id: 'a', source: 'source-two'}] + } as StyleSpecification + ) + ).toEqual([ {command: 'removeLayer', args: ['a']}, {command: 'addLayer', args: [{id: 'a', source: 'source-two'}, undefined]} ]); }); test('add and remove layer on type change', () => { - expect(diff({ - layers: [{id: 'a', type: 'fill'}] - } as StyleSpecification, { - layers: [{id: 'a', type: 'line'}] - } as StyleSpecification)).toEqual([ + expect( + diff( + { + layers: [{id: 'a', type: 'fill'}] + } as StyleSpecification, + { + layers: [{id: 'a', type: 'line'}] + } as StyleSpecification + ) + ).toEqual([ {command: 'removeLayer', args: ['a']}, {command: 'addLayer', args: [{id: 'a', type: 'line'}, undefined]} ]); }); test('add and remove layer on source-layer change', () => { - expect(diff({ - layers: [{id: 'a', source: 'a', 'source-layer': 'layer-one'}] - } as StyleSpecification, { - layers: [{id: 'a', source: 'a', 'source-layer': 'layer-two'}] - } as StyleSpecification)).toEqual([ + expect( + diff( + { + layers: [{id: 'a', source: 'a', 'source-layer': 'layer-one'}] + } as StyleSpecification, + { + layers: [{id: 'a', source: 'a', 'source-layer': 'layer-two'}] + } as StyleSpecification + ) + ).toEqual([ {command: 'removeLayer', args: ['a']}, - {command: 'addLayer', args: [{id: 'a', source: 'a', 'source-layer': 'layer-two'}, undefined]} + { + command: 'addLayer', + args: [{id: 'a', source: 'a', 'source-layer': 'layer-two'}, undefined] + } ]); }); test('add and remove layers on different order and type', () => { - expect(diff({ - layers: [ - {id: 'b'}, - {id: 'c'}, - {id: 'a', type: 'fill'} - ] - } as StyleSpecification, { - layers: [ - {id: 'c'}, - {id: 'a', type: 'line'}, - {id: 'b'} - ] - } as StyleSpecification)).toEqual([ + expect( + diff( + { + layers: [{id: 'b'}, {id: 'c'}, {id: 'a', type: 'fill'}] + } as StyleSpecification, + { + layers: [{id: 'c'}, {id: 'a', type: 'line'}, {id: 'b'}] + } as StyleSpecification + ) + ).toEqual([ {command: 'removeLayer', args: ['b']}, {command: 'addLayer', args: [{id: 'b'}, undefined]}, {command: 'removeLayer', args: ['a']}, @@ -490,21 +657,26 @@ describe('diff', () => { }); test('add and remove layer and source on source data change', () => { - expect(diff({ - sources: {foo: {data: 1}, bar: {}}, - layers: [ - {id: 'a', source: 'bar'}, - {id: 'b', source: 'foo'}, - {id: 'c', source: 'bar'} - ] - } as any as StyleSpecification, { - sources: {foo: {data: 2}, bar: {}}, - layers: [ - {id: 'a', source: 'bar'}, - {id: 'b', source: 'foo'}, - {id: 'c', source: 'bar'} - ] - } as any as StyleSpecification)).toEqual([ + expect( + diff( + { + sources: {foo: {data: 1}, bar: {}}, + layers: [ + {id: 'a', source: 'bar'}, + {id: 'b', source: 'foo'}, + {id: 'c', source: 'bar'} + ] + } as any as StyleSpecification, + { + sources: {foo: {data: 2}, bar: {}}, + layers: [ + {id: 'a', source: 'bar'}, + {id: 'b', source: 'foo'}, + {id: 'c', source: 'bar'} + ] + } as any as StyleSpecification + ) + ).toEqual([ {command: 'removeLayer', args: ['b']}, {command: 'removeSource', args: ['foo']}, {command: 'addSource', args: ['foo', {data: 2}]}, @@ -513,114 +685,141 @@ describe('diff', () => { }); test('set transition', () => { - expect(diff({ - sources: {foo: {data: 1}, bar: {}}, - layers: [ - {id: 'a', source: 'bar'} - ] - } as any as StyleSpecification, { - sources: {foo: {data: 1}, bar: {}}, - layers: [ - {id: 'a', source: 'bar'} - ], - transition: 'transition' - } as any as StyleSpecification)).toEqual([ - {command: 'setTransition', args: ['transition']} - ]); + expect( + diff( + { + sources: {foo: {data: 1}, bar: {}}, + layers: [{id: 'a', source: 'bar'}] + } as any as StyleSpecification, + { + sources: {foo: {data: 1}, bar: {}}, + layers: [{id: 'a', source: 'bar'}], + transition: 'transition' + } as any as StyleSpecification + ) + ).toEqual([{command: 'setTransition', args: ['transition']}]); }); test('no sprite change', () => { - expect(diff({ - sprite: 'a' - } as StyleSpecification, { - sprite: 'a' - } as StyleSpecification)).toEqual([]); + expect( + diff( + { + sprite: 'a' + } as StyleSpecification, + { + sprite: 'a' + } as StyleSpecification + ) + ).toEqual([]); }); test('set sprite', () => { - expect(diff({ - sprite: 'a' - } as StyleSpecification, { - sprite: 'b' - } as StyleSpecification)).toEqual([ - {command: 'setSprite', args: ['b']}, - ]); + expect( + diff( + { + sprite: 'a' + } as StyleSpecification, + { + sprite: 'b' + } as StyleSpecification + ) + ).toEqual([{command: 'setSprite', args: ['b']}]); }); test('set sprite for multiple sprites', () => { - expect(diff({ - sprite: 'a' - } as StyleSpecification, { - sprite: [{'id': 'default', 'url': 'b'}] - } as StyleSpecification)).toEqual([ - {command: 'setSprite', args: [[{'id': 'default', 'url': 'b'}]]}, - ]); + expect( + diff( + { + sprite: 'a' + } as StyleSpecification, + { + sprite: [{id: 'default', url: 'b'}] + } as StyleSpecification + ) + ).toEqual([{command: 'setSprite', args: [[{id: 'default', url: 'b'}]]}]); }); test('no glyphs change', () => { - expect(diff({ - glyphs: 'a' - } as StyleSpecification, { - glyphs: 'a' - } as StyleSpecification)).toEqual([]); + expect( + diff( + { + glyphs: 'a' + } as StyleSpecification, + { + glyphs: 'a' + } as StyleSpecification + ) + ).toEqual([]); }); test('set glyphs', () => { - expect(diff({ - glyphs: 'a' - } as StyleSpecification, { - glyphs: 'b' - } as StyleSpecification)).toEqual([ - {command: 'setGlyphs', args: ['b']}, - ]); + expect( + diff( + { + glyphs: 'a' + } as StyleSpecification, + { + glyphs: 'b' + } as StyleSpecification + ) + ).toEqual([{command: 'setGlyphs', args: ['b']}]); }); test('remove terrain', () => { - expect(diff({ - terrain: { - source: 'maplibre-dem', - exaggeration: 1.5 - } - } as StyleSpecification, { - } as StyleSpecification)).toEqual([ - {command: 'setTerrain', args: [undefined]}, - ]); + expect( + diff( + { + terrain: { + source: 'maplibre-dem', + exaggeration: 1.5 + } + } as StyleSpecification, + {} as StyleSpecification + ) + ).toEqual([{command: 'setTerrain', args: [undefined]}]); }); test('add terrain', () => { - expect(diff({ - } as StyleSpecification, - { - terrain: { - source: 'maplibre-dem', - exaggeration: 1.5 - } - } as StyleSpecification)).toEqual([ - {command: 'setTerrain', args: [{source: 'maplibre-dem', exaggeration: 1.5}]}, - ]); + expect( + diff( + {} as StyleSpecification, + { + terrain: { + source: 'maplibre-dem', + exaggeration: 1.5 + } + } as StyleSpecification + ) + ).toEqual([{command: 'setTerrain', args: [{source: 'maplibre-dem', exaggeration: 1.5}]}]); }); test('set sky', () => { - expect(diff({ - } as StyleSpecification, - { - sky: { - 'fog-color': 'green', - 'fog-ground-blend': 0.2 - } - } as StyleSpecification)).toEqual([ - {command: 'setSky', args: [{'fog-color': 'green', 'fog-ground-blend': 0.2}]}, - ]); + expect( + diff( + {} as StyleSpecification, + { + sky: { + 'fog-color': 'green', + 'fog-ground-blend': 0.2 + } + } as StyleSpecification + ) + ).toEqual([{command: 'setSky', args: [{'fog-color': 'green', 'fog-ground-blend': 0.2}]}]); }); test('set projection', () => { - expect(diff({ - } as StyleSpecification, - { - projection: {type: ['vertical-perspective', 'mercator', 0.5]} - - } as StyleSpecification)).toEqual([ - {command: 'setProjection', args: [{type: ['vertical-perspective', 'mercator', 0.5]}]}, + expect( + diff( + {} as StyleSpecification, + { + projection: {type: ['vertical-perspective', 'mercator', 0.5]} + } as StyleSpecification + ) + ).toEqual([ + { + command: 'setProjection', + args: [{type: ['vertical-perspective', 'mercator', 0.5]}] + } ]); }); }); diff --git a/src/diff.ts b/src/diff.ts index 5f0d60949..2a4751aec 100644 --- a/src/diff.ts +++ b/src/diff.ts @@ -1,5 +1,16 @@ - -import {GeoJSONSourceSpecification, LayerSpecification, LightSpecification, ProjectionSpecification, SkySpecification, SourceSpecification, SpriteSpecification, StyleSpecification, TerrainSpecification, TransitionSpecification, StateSpecification} from './types.g'; +import { + GeoJSONSourceSpecification, + LayerSpecification, + LightSpecification, + ProjectionSpecification, + SkySpecification, + SourceSpecification, + SpriteSpecification, + StyleSpecification, + TerrainSpecification, + TransitionSpecification, + StateSpecification +} from './types.g'; import {deepEqual} from './util/deep_equal'; /** @@ -7,32 +18,32 @@ import {deepEqual} from './util/deep_equal'; * Below are the operations and their arguments, the arguments should be aligned with the style methods in maplibre-gl-js. */ export type DiffOperationsMap = { - 'setStyle': [StyleSpecification]; - 'addLayer': [LayerSpecification, string | null]; - 'removeLayer': [string]; - 'setPaintProperty': [string, string, unknown, string | null]; - 'setLayoutProperty': [string, string, unknown, string | null]; - 'setFilter': [string, unknown]; - 'addSource': [string, SourceSpecification]; - 'removeSource': [string]; - 'setGeoJSONSourceData': [string, unknown]; - 'setLayerZoomRange': [string, number, number]; - 'setLayerProperty': [string, string, unknown]; - 'setCenter': [number[]]; - 'setCenterAltitude': [number]; - 'setZoom': [number]; - 'setBearing': [number]; - 'setPitch': [number]; - 'setRoll': [number]; - 'setSprite': [SpriteSpecification]; - 'setGlyphs': [string]; - 'setTransition': [TransitionSpecification]; - 'setLight': [LightSpecification]; - 'setTerrain': [TerrainSpecification]; - 'setSky': [SkySpecification]; - 'setProjection': [ProjectionSpecification]; - 'setGlobalState': [StateSpecification]; -} + setStyle: [StyleSpecification]; + addLayer: [LayerSpecification, string | null]; + removeLayer: [string]; + setPaintProperty: [string, string, unknown, string | null]; + setLayoutProperty: [string, string, unknown, string | null]; + setFilter: [string, unknown]; + addSource: [string, SourceSpecification]; + removeSource: [string]; + setGeoJSONSourceData: [string, unknown]; + setLayerZoomRange: [string, number, number]; + setLayerProperty: [string, string, unknown]; + setCenter: [number[]]; + setCenterAltitude: [number]; + setZoom: [number]; + setBearing: [number]; + setPitch: [number]; + setRoll: [number]; + setSprite: [SpriteSpecification]; + setGlyphs: [string]; + setTransition: [TransitionSpecification]; + setLight: [LightSpecification]; + setTerrain: [TerrainSpecification]; + setSky: [SkySpecification]; + setProjection: [ProjectionSpecification]; + setGlobalState: [StateSpecification]; +}; export type DiffOperations = keyof DiffOperationsMap; @@ -46,25 +57,45 @@ export type DiffCommand = { * @param commands - The commands array to add to * @param command - The command to add */ -function addCommand(commands: DiffCommand[], command: DiffCommand) { +function addCommand( + commands: DiffCommand[], + command: DiffCommand +) { commands.push(command); } -function addSource(sourceId: string, after: {[key: string]: SourceSpecification}, commands: DiffCommand[]) { +function addSource( + sourceId: string, + after: {[key: string]: SourceSpecification}, + commands: DiffCommand[] +) { addCommand(commands, {command: 'addSource', args: [sourceId, after[sourceId]]}); } -function removeSource(sourceId: string, commands: DiffCommand[], sourcesRemoved: {[key: string]: boolean}) { +function removeSource( + sourceId: string, + commands: DiffCommand[], + sourcesRemoved: {[key: string]: boolean} +) { addCommand(commands, {command: 'removeSource', args: [sourceId]}); sourcesRemoved[sourceId] = true; } -function updateSource(sourceId: string, after: {[key: string]: SourceSpecification}, commands: DiffCommand[], sourcesRemoved: {[key: string]: boolean}) { +function updateSource( + sourceId: string, + after: {[key: string]: SourceSpecification}, + commands: DiffCommand[], + sourcesRemoved: {[key: string]: boolean} +) { removeSource(sourceId, commands, sourcesRemoved); addSource(sourceId, after, commands); } -function canUpdateGeoJSON(before: {[key: string]: SourceSpecification}, after: {[key: string]: SourceSpecification}, sourceId: string) { +function canUpdateGeoJSON( + before: {[key: string]: SourceSpecification}, + after: {[key: string]: SourceSpecification}, + sourceId: string +) { let prop; for (prop in before[sourceId]) { if (!Object.prototype.hasOwnProperty.call(before[sourceId], prop)) continue; @@ -81,9 +112,14 @@ function canUpdateGeoJSON(before: {[key: string]: SourceSpecification}, after: { return true; } -function diffSources(before: {[key: string]: SourceSpecification}, after: {[key: string]: SourceSpecification}, commands: DiffCommand[], sourcesRemoved: {[key: string]: boolean}) { - before = before || {} as {[key: string]: SourceSpecification}; - after = after || {} as {[key: string]: SourceSpecification}; +function diffSources( + before: {[key: string]: SourceSpecification}, + after: {[key: string]: SourceSpecification}, + commands: DiffCommand[], + sourcesRemoved: {[key: string]: boolean} +) { + before = before || ({} as {[key: string]: SourceSpecification}); + after = after || ({} as {[key: string]: SourceSpecification}); let sourceId: string; @@ -101,8 +137,15 @@ function diffSources(before: {[key: string]: SourceSpecification}, after: {[key: if (!Object.prototype.hasOwnProperty.call(before, sourceId)) { addSource(sourceId, after, commands); } else if (!deepEqual(before[sourceId], after[sourceId])) { - if (before[sourceId].type === 'geojson' && after[sourceId].type === 'geojson' && canUpdateGeoJSON(before, after, sourceId)) { - addCommand(commands, {command: 'setGeoJSONSourceData', args: [sourceId, (after[sourceId] as GeoJSONSourceSpecification).data]}); + if ( + before[sourceId].type === 'geojson' && + after[sourceId].type === 'geojson' && + canUpdateGeoJSON(before, after, sourceId) + ) { + addCommand(commands, { + command: 'setGeoJSONSourceData', + args: [sourceId, (after[sourceId] as GeoJSONSourceSpecification).data] + }); } else { // no update command, must remove then add updateSource(sourceId, after, commands, sourcesRemoved); @@ -111,9 +154,16 @@ function diffSources(before: {[key: string]: SourceSpecification}, after: {[key: } } -function diffLayerPropertyChanges(before: LayerSpecification['layout'] | LayerSpecification['paint'], after:LayerSpecification['layout'] | LayerSpecification['paint'], commands: DiffCommand[], layerId: string, klass: string | null, command: 'setPaintProperty' | 'setLayoutProperty') { - before = before || {} as LayerSpecification['layout'] | LayerSpecification['paint']; - after = after || {} as LayerSpecification['layout'] | LayerSpecification['paint']; +function diffLayerPropertyChanges( + before: LayerSpecification['layout'] | LayerSpecification['paint'], + after: LayerSpecification['layout'] | LayerSpecification['paint'], + commands: DiffCommand[], + layerId: string, + klass: string | null, + command: 'setPaintProperty' | 'setLayoutProperty' +) { + before = before || ({} as LayerSpecification['layout'] | LayerSpecification['paint']); + after = after || ({} as LayerSpecification['layout'] | LayerSpecification['paint']); for (const prop in before) { if (!Object.prototype.hasOwnProperty.call(before, prop)) continue; @@ -122,7 +172,11 @@ function diffLayerPropertyChanges(before: LayerSpecification['layout'] | LayerSp } } for (const prop in after) { - if (!Object.prototype.hasOwnProperty.call(after, prop) || Object.prototype.hasOwnProperty.call(before, prop)) continue; + if ( + !Object.prototype.hasOwnProperty.call(after, prop) || + Object.prototype.hasOwnProperty.call(before, prop) + ) + continue; if (!deepEqual(before[prop], after[prop])) { commands.push({command, args: [layerId, prop, after[prop], klass]}); } @@ -137,7 +191,11 @@ function indexById(group: {[key: string]: LayerSpecification}, layer: LayerSpeci return group; } -function diffLayers(before: LayerSpecification[], after: LayerSpecification[], commands: DiffCommand[]) { +function diffLayers( + before: LayerSpecification[], + after: LayerSpecification[], + commands: DiffCommand[] +) { before = before || []; after = after || []; @@ -156,8 +214,8 @@ function diffLayers(before: LayerSpecification[], after: LayerSpecification[], c const clean = Object.create(null); let layerId: string; - let beforeLayer: LayerSpecification & { source?: string; filter?: unknown}; - let afterLayer: LayerSpecification & { source?: string; filter?: unknown}; + let beforeLayer: LayerSpecification & {source?: string; filter?: unknown}; + let afterLayer: LayerSpecification & {source?: string; filter?: unknown}; let insertBeforeLayerId: string; let prop: string; @@ -191,7 +249,10 @@ function diffLayers(before: LayerSpecification[], after: LayerSpecification[], c // add layer at correct position insertBeforeLayerId = tracker[tracker.length - i]; - addCommand(commands, {command: 'addLayer', args: [afterIndex[layerId], insertBeforeLayerId]}); + addCommand(commands, { + command: 'addLayer', + args: [afterIndex[layerId], insertBeforeLayerId] + }); tracker.splice(tracker.length - i, 0, layerId); clean[layerId] = true; } @@ -207,7 +268,11 @@ function diffLayers(before: LayerSpecification[], after: LayerSpecification[], c // If source, source-layer, or type have changes, then remove the layer // and add it back 'from scratch'. - if (!deepEqual(beforeLayer.source, afterLayer.source) || !deepEqual(beforeLayer['source-layer'], afterLayer['source-layer']) || !deepEqual(beforeLayer.type, afterLayer.type)) { + if ( + !deepEqual(beforeLayer.source, afterLayer.source) || + !deepEqual(beforeLayer['source-layer'], afterLayer['source-layer']) || + !deepEqual(beforeLayer.type, afterLayer.type) + ) { addCommand(commands, {command: 'removeLayer', args: [layerId]}); // we add the layer back at the same position it was already in, so // there's no need to update the `tracker` @@ -217,34 +282,92 @@ function diffLayers(before: LayerSpecification[], after: LayerSpecification[], c } // layout, paint, filter, minzoom, maxzoom - diffLayerPropertyChanges(beforeLayer.layout, afterLayer.layout, commands, layerId, null, 'setLayoutProperty'); - diffLayerPropertyChanges(beforeLayer.paint, afterLayer.paint, commands, layerId, null, 'setPaintProperty'); + diffLayerPropertyChanges( + beforeLayer.layout, + afterLayer.layout, + commands, + layerId, + null, + 'setLayoutProperty' + ); + diffLayerPropertyChanges( + beforeLayer.paint, + afterLayer.paint, + commands, + layerId, + null, + 'setPaintProperty' + ); if (!deepEqual(beforeLayer.filter, afterLayer.filter)) { addCommand(commands, {command: 'setFilter', args: [layerId, afterLayer.filter]}); } - if (!deepEqual(beforeLayer.minzoom, afterLayer.minzoom) || !deepEqual(beforeLayer.maxzoom, afterLayer.maxzoom)) { - addCommand(commands, {command: 'setLayerZoomRange', args: [layerId, afterLayer.minzoom, afterLayer.maxzoom]}); + if ( + !deepEqual(beforeLayer.minzoom, afterLayer.minzoom) || + !deepEqual(beforeLayer.maxzoom, afterLayer.maxzoom) + ) { + addCommand(commands, { + command: 'setLayerZoomRange', + args: [layerId, afterLayer.minzoom, afterLayer.maxzoom] + }); } // handle all other layer props, including paint.* for (prop in beforeLayer) { if (!Object.prototype.hasOwnProperty.call(beforeLayer, prop)) continue; - if (prop === 'layout' || prop === 'paint' || prop === 'filter' || - prop === 'metadata' || prop === 'minzoom' || prop === 'maxzoom') continue; + if ( + prop === 'layout' || + prop === 'paint' || + prop === 'filter' || + prop === 'metadata' || + prop === 'minzoom' || + prop === 'maxzoom' + ) + continue; if (prop.indexOf('paint.') === 0) { - diffLayerPropertyChanges(beforeLayer[prop], afterLayer[prop], commands, layerId, prop.slice(6), 'setPaintProperty'); + diffLayerPropertyChanges( + beforeLayer[prop], + afterLayer[prop], + commands, + layerId, + prop.slice(6), + 'setPaintProperty' + ); } else if (!deepEqual(beforeLayer[prop], afterLayer[prop])) { - addCommand(commands, {command: 'setLayerProperty', args: [layerId, prop, afterLayer[prop]]}); + addCommand(commands, { + command: 'setLayerProperty', + args: [layerId, prop, afterLayer[prop]] + }); } } for (prop in afterLayer) { - if (!Object.prototype.hasOwnProperty.call(afterLayer, prop) || Object.prototype.hasOwnProperty.call(beforeLayer, prop)) continue; - if (prop === 'layout' || prop === 'paint' || prop === 'filter' || - prop === 'metadata' || prop === 'minzoom' || prop === 'maxzoom') continue; + if ( + !Object.prototype.hasOwnProperty.call(afterLayer, prop) || + Object.prototype.hasOwnProperty.call(beforeLayer, prop) + ) + continue; + if ( + prop === 'layout' || + prop === 'paint' || + prop === 'filter' || + prop === 'metadata' || + prop === 'minzoom' || + prop === 'maxzoom' + ) + continue; if (prop.indexOf('paint.') === 0) { - diffLayerPropertyChanges(beforeLayer[prop], afterLayer[prop], commands, layerId, prop.slice(6), 'setPaintProperty'); + diffLayerPropertyChanges( + beforeLayer[prop], + afterLayer[prop], + commands, + layerId, + prop.slice(6), + 'setPaintProperty' + ); } else if (!deepEqual(beforeLayer[prop], afterLayer[prop])) { - addCommand(commands, {command: 'setLayerProperty', args: [layerId, prop, afterLayer[prop]]}); + addCommand(commands, { + command: 'setLayerProperty', + args: [layerId, prop, afterLayer[prop]] + }); } } } @@ -268,7 +391,10 @@ function diffLayers(before: LayerSpecification[], after: LayerSpecification[], c * @param {*} after stylesheet to compare to * @returns Array list of changes */ -export function diff(before: StyleSpecification, after: StyleSpecification): DiffCommand[] { +export function diff( + before: StyleSpecification, + after: StyleSpecification +): DiffCommand[] { if (!before) return [{command: 'setStyle', args: [after]}]; let commands: DiffCommand[] = []; @@ -349,7 +475,6 @@ export function diff(before: StyleSpecification, after: StyleSpecification): Dif // Handle changes to `layers` diffLayers(beforeLayers, after.layers, commands); - } catch (e) { // fall back to setStyle console.warn('Unable to compute style diff:', e); diff --git a/src/error/validation_error.ts b/src/error/validation_error.ts index cd958cf34..9b6fad0f1 100644 --- a/src/error/validation_error.ts +++ b/src/error/validation_error.ts @@ -5,9 +5,14 @@ export class ValidationError { identifier: string; line: number; - constructor(key: string, value: any & { - __line__: number; - }, message: string, identifier?: string | null) { + constructor( + key: string, + value: any & { + __line__: number; + }, + message: string, + identifier?: string | null + ) { this.message = (key ? `${key}: ` : '') + message; if (identifier) this.identifier = identifier; diff --git a/src/expression/compound_expression.ts b/src/expression/compound_expression.ts index 7d0fc680c..42da2d1f3 100644 --- a/src/expression/compound_expression.ts +++ b/src/expression/compound_expression.ts @@ -1,4 +1,5 @@ -import {typeToString, +import { + typeToString, NumberType, StringType, BooleanType, @@ -7,13 +8,13 @@ import {typeToString, ValueType, ErrorType, CollatorType, - array, + array } from './types'; import {ParsingContext} from './parsing_context'; import {EvaluationContext} from './evaluation_context'; -import {expressions} from './definitions/index'; +import {expressions} from './definitions'; import {CollatorExpression} from './definitions/collator'; import {Within} from './definitions/within'; import {Literal} from './definitions/literal'; @@ -37,10 +38,12 @@ export type Varargs = { type Signature = Array | Varargs; type Evaluate = (b: EvaluationContext, a: Array) => Value; -type Definition = [Type, Signature, Evaluate] | { - type: Type; - overloads: Array<[Signature, Evaluate]>; -}; +type Definition = + | [Type, Signature, Evaluate] + | { + type: Type; + overloads: Array<[Signature, Evaluate]>; + }; export class CompoundExpression implements Expression { name: string; @@ -70,31 +73,40 @@ export class CompoundExpression implements Expression { } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - const op: string = (args[0] as any); + const op: string = args[0] as any; const definition = CompoundExpression.definitions[op]; if (!definition) { - return context.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0) as null; + return context.error( + `Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, + 0 + ) as null; } // Now check argument types against each signature - const type = Array.isArray(definition) ? - definition[0] : definition.type; + const type = Array.isArray(definition) ? definition[0] : definition.type; - const availableOverloads = Array.isArray(definition) ? - [[definition[1], definition[2]]] : - definition.overloads; + const availableOverloads = Array.isArray(definition) + ? [[definition[1], definition[2]]] + : definition.overloads; - const overloads = availableOverloads.filter(([signature]) => ( - !Array.isArray(signature) || // varags - signature.length === args.length - 1 // correct param count - )); + const overloads = availableOverloads.filter( + ([signature]) => + !Array.isArray(signature) || // varags + signature.length === args.length - 1 // correct param count + ); let signatureContext: ParsingContext = null; for (const [params, evaluate] of overloads) { // Use a fresh context for each attempted signature so that, if // we eventually succeed, we haven't polluted `context.errors`. - signatureContext = new ParsingContext(context.registry, isExpressionConstant, context.path, null, context.scope); + signatureContext = new ParsingContext( + context.registry, + isExpressionConstant, + context.path, + null, + context.scope + ); // First parse all the args, potentially coercing to the // types expected by this overload. @@ -102,9 +114,9 @@ export class CompoundExpression implements Expression { let argParseFailed = false; for (let i = 1; i < args.length; i++) { const arg = args[i]; - const expectedType = Array.isArray(params) ? - params[i - 1] : - (params as Varargs).type; + const expectedType = Array.isArray(params) + ? params[i - 1] + : (params as Varargs).type; const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType); if (!parsed) { @@ -121,7 +133,9 @@ export class CompoundExpression implements Expression { if (Array.isArray(params)) { if (params.length !== parsedArgs.length) { - signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`); + signatureContext.error( + `Expected ${params.length} arguments, but found ${parsedArgs.length} instead.` + ); continue; } } @@ -153,16 +167,15 @@ export class CompoundExpression implements Expression { if (!parsed) return null; actualTypes.push(typeToString(parsed.type)); } - context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`); + context.error( + `Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.` + ); } return null; } - static register( - registry: ExpressionRegistry, - definitions: {[_: string]: Definition} - ) { + static register(registry: ExpressionRegistry, definitions: {[_: string]: Definition}) { CompoundExpression.definitions = definitions; for (const name in definitions) { registry[name] = CompoundExpression; @@ -192,12 +205,9 @@ function get(key, obj) { function binarySearch(v, a, i, j) { while (i <= j) { const m = (i + j) >> 1; - if (a[m] === v) - return true; - if (a[m] > v) - j = m - 1; - else - i = m + 1; + if (a[m] === v) return true; + if (a[m] > v) j = m - 1; + else i = m + 1; } return false; } @@ -207,53 +217,39 @@ function varargs(type: Type): Varargs { } CompoundExpression.register(expressions, { - 'error': [ + error: [ ErrorType, [StringType], - (ctx, [v]) => { throw new RuntimeError(v.evaluate(ctx)); } - ], - 'typeof': [ - StringType, - [ValueType], - (ctx, [v]) => typeToString(typeOf(v.evaluate(ctx))) + (ctx, [v]) => { + throw new RuntimeError(v.evaluate(ctx)); + } ], + typeof: [StringType, [ValueType], (ctx, [v]) => typeToString(typeOf(v.evaluate(ctx)))], 'to-rgba': [ array(NumberType, 4), [ColorType], (ctx, [v]) => { const [r, g, b, a] = v.evaluate(ctx).rgb; return [r * 255, g * 255, b * 255, a]; - }, - ], - 'rgb': [ - ColorType, - [NumberType, NumberType, NumberType], - rgba - ], - 'rgba': [ - ColorType, - [NumberType, NumberType, NumberType, NumberType], - rgba + } ], - 'has': { + rgb: [ColorType, [NumberType, NumberType, NumberType], rgba], + rgba: [ColorType, [NumberType, NumberType, NumberType, NumberType], rgba], + has: { type: BooleanType, overloads: [ + [[StringType], (ctx, [key]) => has(key.evaluate(ctx), ctx.properties())], [ - [StringType], - (ctx, [key]) => has(key.evaluate(ctx), ctx.properties()) - ], [ [StringType, ObjectType], (ctx, [key, obj]) => has(key.evaluate(ctx), obj.evaluate(ctx)) ] ] }, - 'get': { + get: { type: ValueType, overloads: [ + [[StringType], (ctx, [key]) => get(key.evaluate(ctx), ctx.properties())], [ - [StringType], - (ctx, [key]) => get(key.evaluate(ctx), ctx.properties()) - ], [ [StringType, ObjectType], (ctx, [key, obj]) => get(key.evaluate(ctx), obj.evaluate(ctx)) ] @@ -264,45 +260,17 @@ CompoundExpression.register(expressions, { [StringType], (ctx, [key]) => get(key.evaluate(ctx), ctx.featureState || {}) ], - 'properties': [ - ObjectType, - [], - (ctx) => ctx.properties() - ], - 'geometry-type': [ - StringType, - [], - (ctx) => ctx.geometryType() - ], - 'id': [ + properties: [ObjectType, [], (ctx) => ctx.properties()], + 'geometry-type': [StringType, [], (ctx) => ctx.geometryType()], + id: [ValueType, [], (ctx) => ctx.id()], + zoom: [NumberType, [], (ctx) => ctx.globals.zoom], + 'heatmap-density': [NumberType, [], (ctx) => ctx.globals.heatmapDensity || 0], + elevation: [NumberType, [], (ctx) => ctx.globals.elevation || 0], + 'line-progress': [NumberType, [], (ctx) => ctx.globals.lineProgress || 0], + accumulated: [ ValueType, [], - (ctx) => ctx.id() - ], - 'zoom': [ - NumberType, - [], - (ctx) => ctx.globals.zoom - ], - 'heatmap-density': [ - NumberType, - [], - (ctx) => ctx.globals.heatmapDensity || 0 - ], - 'elevation': [ - NumberType, - [], - (ctx) => ctx.globals.elevation || 0 - ], - 'line-progress': [ - NumberType, - [], - (ctx) => ctx.globals.lineProgress || 0 - ], - 'accumulated': [ - ValueType, - [], - (ctx) => ctx.globals.accumulated === undefined ? null : ctx.globals.accumulated + (ctx) => (ctx.globals.accumulated === undefined ? null : ctx.globals.accumulated) ], '+': [ NumberType, @@ -329,111 +297,42 @@ CompoundExpression.register(expressions, { '-': { type: NumberType, overloads: [ - [ - [NumberType, NumberType], - (ctx, [a, b]) => a.evaluate(ctx) - b.evaluate(ctx) - ], [ - [NumberType], - (ctx, [a]) => -a.evaluate(ctx) - ] + [[NumberType, NumberType], (ctx, [a, b]) => a.evaluate(ctx) - b.evaluate(ctx)], + [[NumberType], (ctx, [a]) => -a.evaluate(ctx)] ] }, - '/': [ - NumberType, - [NumberType, NumberType], - (ctx, [a, b]) => a.evaluate(ctx) / b.evaluate(ctx) - ], - '%': [ - NumberType, - [NumberType, NumberType], - (ctx, [a, b]) => a.evaluate(ctx) % b.evaluate(ctx) - ], - 'ln2': [ - NumberType, - [], - () => Math.LN2 - ], - 'pi': [ - NumberType, - [], - () => Math.PI - ], - 'e': [ - NumberType, - [], - () => Math.E - ], + '/': [NumberType, [NumberType, NumberType], (ctx, [a, b]) => a.evaluate(ctx) / b.evaluate(ctx)], + '%': [NumberType, [NumberType, NumberType], (ctx, [a, b]) => a.evaluate(ctx) % b.evaluate(ctx)], + ln2: [NumberType, [], () => Math.LN2], + pi: [NumberType, [], () => Math.PI], + e: [NumberType, [], () => Math.E], '^': [ NumberType, [NumberType, NumberType], (ctx, [b, e]) => Math.pow(b.evaluate(ctx), e.evaluate(ctx)) ], - 'sqrt': [ - NumberType, - [NumberType], - (ctx, [x]) => Math.sqrt(x.evaluate(ctx)) - ], - 'log10': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN10 - ], - 'ln': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.log(n.evaluate(ctx)) - ], - 'log2': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN2 - ], - 'sin': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.sin(n.evaluate(ctx)) - ], - 'cos': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.cos(n.evaluate(ctx)) - ], - 'tan': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.tan(n.evaluate(ctx)) - ], - 'asin': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.asin(n.evaluate(ctx)) - ], - 'acos': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.acos(n.evaluate(ctx)) - ], - 'atan': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.atan(n.evaluate(ctx)) - ], - 'min': [ + sqrt: [NumberType, [NumberType], (ctx, [x]) => Math.sqrt(x.evaluate(ctx))], + log10: [NumberType, [NumberType], (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN10], + ln: [NumberType, [NumberType], (ctx, [n]) => Math.log(n.evaluate(ctx))], + log2: [NumberType, [NumberType], (ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN2], + sin: [NumberType, [NumberType], (ctx, [n]) => Math.sin(n.evaluate(ctx))], + cos: [NumberType, [NumberType], (ctx, [n]) => Math.cos(n.evaluate(ctx))], + tan: [NumberType, [NumberType], (ctx, [n]) => Math.tan(n.evaluate(ctx))], + asin: [NumberType, [NumberType], (ctx, [n]) => Math.asin(n.evaluate(ctx))], + acos: [NumberType, [NumberType], (ctx, [n]) => Math.acos(n.evaluate(ctx))], + atan: [NumberType, [NumberType], (ctx, [n]) => Math.atan(n.evaluate(ctx))], + min: [ NumberType, varargs(NumberType), - (ctx, args) => Math.min(...args.map(arg => arg.evaluate(ctx))) + (ctx, args) => Math.min(...args.map((arg) => arg.evaluate(ctx))) ], - 'max': [ + max: [ NumberType, varargs(NumberType), - (ctx, args) => Math.max(...args.map(arg => arg.evaluate(ctx))) - ], - 'abs': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.abs(n.evaluate(ctx)) + (ctx, args) => Math.max(...args.map((arg) => arg.evaluate(ctx))) ], - 'round': [ + abs: [NumberType, [NumberType], (ctx, [n]) => Math.abs(n.evaluate(ctx))], + round: [ NumberType, [NumberType], (ctx, [n]) => { @@ -444,26 +343,14 @@ CompoundExpression.register(expressions, { return v < 0 ? -Math.round(-v) : Math.round(v); } ], - 'floor': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.floor(n.evaluate(ctx)) - ], - 'ceil': [ - NumberType, - [NumberType], - (ctx, [n]) => Math.ceil(n.evaluate(ctx)) - ], + floor: [NumberType, [NumberType], (ctx, [n]) => Math.floor(n.evaluate(ctx))], + ceil: [NumberType, [NumberType], (ctx, [n]) => Math.ceil(n.evaluate(ctx))], 'filter-==': [ BooleanType, [StringType, ValueType], (ctx, [k, v]) => ctx.properties()[(k as any).value] === (v as any).value ], - 'filter-id-==': [ - BooleanType, - [ValueType], - (ctx, [v]) => ctx.id() === (v as any).value - ], + 'filter-id-==': [BooleanType, [ValueType], (ctx, [v]) => ctx.id() === (v as any).value], 'filter-type-==': [ BooleanType, [StringType], @@ -541,16 +428,8 @@ CompoundExpression.register(expressions, { return typeof a === typeof b && a >= b; } ], - 'filter-has': [ - BooleanType, - [ValueType], - (ctx, [k]) => (k as any).value in ctx.properties() - ], - 'filter-has-id': [ - BooleanType, - [], - (ctx) => (ctx.id() !== null && ctx.id() !== undefined) - ], + 'filter-has': [BooleanType, [ValueType], (ctx, [k]) => (k as any).value in ctx.properties()], + 'filter-has-id': [BooleanType, [], (ctx) => ctx.id() !== null && ctx.id() !== undefined], 'filter-type-in': [ BooleanType, [array(StringType)], @@ -571,51 +450,45 @@ CompoundExpression.register(expressions, { BooleanType, [StringType, array(ValueType)], // assumes v is a array literal with values sorted in ascending order and of a single type - (ctx, [k, v]) => binarySearch(ctx.properties()[(k as any).value], (v as any).value, 0, (v as any).value.length - 1) - ], - 'all': { + (ctx, [k, v]) => + binarySearch( + ctx.properties()[(k as any).value], + (v as any).value, + 0, + (v as any).value.length - 1 + ) + ], + all: { type: BooleanType, overloads: [ - [ - [BooleanType, BooleanType], - (ctx, [a, b]) => a.evaluate(ctx) && b.evaluate(ctx) - ], + [[BooleanType, BooleanType], (ctx, [a, b]) => a.evaluate(ctx) && b.evaluate(ctx)], [ varargs(BooleanType), (ctx, args) => { for (const arg of args) { - if (!arg.evaluate(ctx)) - return false; + if (!arg.evaluate(ctx)) return false; } return true; } ] ] }, - 'any': { + any: { type: BooleanType, overloads: [ - [ - [BooleanType, BooleanType], - (ctx, [a, b]) => a.evaluate(ctx) || b.evaluate(ctx) - ], + [[BooleanType, BooleanType], (ctx, [a, b]) => a.evaluate(ctx) || b.evaluate(ctx)], [ varargs(BooleanType), (ctx, args) => { for (const arg of args) { - if (arg.evaluate(ctx)) - return true; + if (arg.evaluate(ctx)) return true; } return false; } ] ] }, - '!': [ - BooleanType, - [BooleanType], - (ctx, [b]) => !b.evaluate(ctx) - ], + '!': [BooleanType, [BooleanType], (ctx, [b]) => !b.evaluate(ctx)], 'is-supported-script': [ BooleanType, [StringType], @@ -628,20 +501,12 @@ CompoundExpression.register(expressions, { return true; } ], - 'upcase': [ - StringType, - [StringType], - (ctx, [s]) => s.evaluate(ctx).toUpperCase() - ], - 'downcase': [ - StringType, - [StringType], - (ctx, [s]) => s.evaluate(ctx).toLowerCase() - ], - 'concat': [ + upcase: [StringType, [StringType], (ctx, [s]) => s.evaluate(ctx).toUpperCase()], + downcase: [StringType, [StringType], (ctx, [s]) => s.evaluate(ctx).toLowerCase()], + concat: [ StringType, varargs(ValueType), - (ctx, args) => args.map(arg => valueToString(arg.evaluate(ctx))).join('') + (ctx, args) => args.map((arg) => valueToString(arg.evaluate(ctx))).join('') ], 'resolved-locale': [ StringType, @@ -676,11 +541,10 @@ function isExpressionConstant(expression: Expression) { return false; } - const isTypeAnnotation = expression instanceof Coercion || - expression instanceof Assertion; + const isTypeAnnotation = expression instanceof Coercion || expression instanceof Assertion; let childrenConstant = true; - expression.eachChild(child => { + expression.eachChild((child) => { // We can _almost_ assume that if `expressions` children are constant, // they would already have been evaluated to Literal values when they // were parsed. Type annotations are the exception, because they might @@ -698,9 +562,17 @@ function isExpressionConstant(expression: Expression) { return false; } - return isFeatureConstant(expression) && - isGlobalPropertyConstant(expression, - ['zoom', 'heatmap-density', 'elevation', 'line-progress', 'accumulated', 'is-supported-script']); + return ( + isFeatureConstant(expression) && + isGlobalPropertyConstant(expression, [ + 'zoom', + 'heatmap-density', + 'elevation', + 'line-progress', + 'accumulated', + 'is-supported-script' + ]) + ); } function isFeatureConstant(e: Expression) { @@ -711,11 +583,7 @@ function isFeatureConstant(e: Expression) { return false; } else if (e.name === 'has' && e.args.length === 1) { return false; - } else if ( - e.name === 'properties' || - e.name === 'geometry-type' || - e.name === 'id' - ) { + } else if (e.name === 'properties' || e.name === 'geometry-type' || e.name === 'id') { return false; } else if (/^filter-/.test(e.name)) { return false; @@ -730,8 +598,10 @@ function isFeatureConstant(e: Expression) { } let result = true; - e.eachChild(arg => { - if (result && !isFeatureConstant(arg)) { result = false; } + e.eachChild((arg) => { + if (result && !isFeatureConstant(arg)) { + result = false; + } }); return result; } @@ -743,17 +613,23 @@ function isStateConstant(e: Expression) { } } let result = true; - e.eachChild(arg => { - if (result && !isStateConstant(arg)) { result = false; } + e.eachChild((arg) => { + if (result && !isStateConstant(arg)) { + result = false; + } }); return result; } function isGlobalPropertyConstant(e: Expression, properties: Array) { - if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) { return false; } + if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) { + return false; + } let result = true; e.eachChild((arg) => { - if (result && !isGlobalPropertyConstant(arg, properties)) { result = false; } + if (result && !isGlobalPropertyConstant(arg, properties)) { + result = false; + } }); return result; } diff --git a/src/expression/definitions/assertion.ts b/src/expression/definitions/assertion.ts index 2db761480..28f4cc401 100644 --- a/src/expression/definitions/assertion.ts +++ b/src/expression/definitions/assertion.ts @@ -1,4 +1,3 @@ - import { ObjectType, ValueType, @@ -34,19 +33,21 @@ export class Assertion implements Expression { } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - if (args.length < 2) - return context.error('Expected at least one argument.') as null; + if (args.length < 2) return context.error('Expected at least one argument.') as null; let i = 1; let type; - const name: string = (args[0] as any); + const name: string = args[0] as any; if (name === 'array') { let itemType; if (args.length > 2) { const type = args[1]; if (typeof type !== 'string' || !(type in types) || type === 'object') - return context.error('The item type argument of "array" must be one of string, number, boolean', 1) as null; + return context.error( + 'The item type argument of "array" must be one of string, number, boolean', + 1 + ) as null; itemType = types[type]; i++; } else { @@ -55,12 +56,14 @@ export class Assertion implements Expression { let N; if (args.length > 3) { - if (args[2] !== null && - (typeof args[2] !== 'number' || - args[2] < 0 || - args[2] !== Math.floor(args[2])) + if ( + args[2] !== null && + (typeof args[2] !== 'number' || args[2] < 0 || args[2] !== Math.floor(args[2])) ) { - return context.error('The length argument to "array" must be a positive integer literal', 2) as null; + return context.error( + 'The length argument to "array" must be a positive integer literal', + 2 + ) as null; } N = args[2]; i++; @@ -89,7 +92,9 @@ export class Assertion implements Expression { if (!error) { return value; } else if (i === this.args.length - 1) { - throw new RuntimeError(`Expected value to be of type ${typeToString(this.type)}, but found ${typeToString(typeOf(value))} instead.`); + throw new RuntimeError( + `Expected value to be of type ${typeToString(this.type)}, but found ${typeToString(typeOf(value))} instead.` + ); } } @@ -101,6 +106,6 @@ export class Assertion implements Expression { } outputDefined(): boolean { - return this.args.every(arg => arg.outputDefined()); + return this.args.every((arg) => arg.outputDefined()); } } diff --git a/src/expression/definitions/at.ts b/src/expression/definitions/at.ts index 7b902dac8..4bc433edc 100644 --- a/src/expression/definitions/at.ts +++ b/src/expression/definitions/at.ts @@ -21,20 +21,22 @@ export class At implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 3) - return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`) as null; + return context.error( + `Expected 2 arguments, but found ${args.length - 1} instead.` + ) as null; const index = context.parse(args[1], 1, NumberType); const input = context.parse(args[2], 2, array(context.expectedType || ValueType)); if (!index || !input) return null; - const t: ArrayType = (input.type as any); + const t: ArrayType = input.type as any; return new At(t.itemType, index, input); } evaluate(ctx: EvaluationContext) { - const index = (this.index.evaluate(ctx) as any as number); - const array = (this.input.evaluate(ctx) as any as Array); + const index = this.index.evaluate(ctx) as any as number; + const array = this.input.evaluate(ctx) as any as Array; if (index < 0) { throw new RuntimeError(`Array index out of bounds: ${index} < 0.`); @@ -60,4 +62,3 @@ export class At implements Expression { return false; } } - diff --git a/src/expression/definitions/case.ts b/src/expression/definitions/case.ts index 422684124..7c8563bf8 100644 --- a/src/expression/definitions/case.ts +++ b/src/expression/definitions/case.ts @@ -1,4 +1,3 @@ - import {BooleanType} from '../types'; import type {Expression} from '../expression'; @@ -22,7 +21,9 @@ export class Case implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length < 4) - return context.error(`Expected at least 3 arguments, but found only ${args.length - 1}.`) as null; + return context.error( + `Expected at least 3 arguments, but found only ${args.length - 1}.` + ) as null; if (args.length % 2 !== 0) return context.error('Expected an odd number of arguments.') as null; @@ -47,8 +48,8 @@ export class Case implements Expression { const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType); if (!otherwise) return null; - if (!outputType) throw new Error('Can\'t infer output type'); - return new Case((outputType as any), branches, otherwise); + if (!outputType) throw new Error("Can't infer output type"); + return new Case(outputType as any, branches, otherwise); } evaluate(ctx: EvaluationContext) { @@ -69,7 +70,8 @@ export class Case implements Expression { } outputDefined(): boolean { - return this.branches.every(([_, out]) => out.outputDefined()) && this.otherwise.outputDefined(); + return ( + this.branches.every(([_, out]) => out.outputDefined()) && this.otherwise.outputDefined() + ); } } - diff --git a/src/expression/definitions/coalesce.ts b/src/expression/definitions/coalesce.ts index 3e615d01d..88044da45 100644 --- a/src/expression/definitions/coalesce.ts +++ b/src/expression/definitions/coalesce.ts @@ -27,7 +27,9 @@ export class Coalesce implements Expression { const parsedArgs = []; for (const arg of args.slice(1)) { - const parsed = context.parse(arg, 1 + parsedArgs.length, outputType, undefined, {typeAnnotation: 'omit'}); + const parsed = context.parse(arg, 1 + parsedArgs.length, outputType, undefined, { + typeAnnotation: 'omit' + }); if (!parsed) return null; outputType = outputType || parsed.type; parsedArgs.push(parsed); @@ -39,12 +41,12 @@ export class Coalesce implements Expression { // preempt the desired null-coalescing behavior. // Thus, if any of our arguments would have needed an annotation, we // need to wrap the enclosing coalesce expression with it instead. - const needsAnnotation = expectedType && - parsedArgs.some(arg => checkSubtype(expectedType, arg.type)); + const needsAnnotation = + expectedType && parsedArgs.some((arg) => checkSubtype(expectedType, arg.type)); - return needsAnnotation ? - new Coalesce(ValueType, parsedArgs) : - new Coalesce((outputType as any), parsedArgs); + return needsAnnotation + ? new Coalesce(ValueType, parsedArgs) + : new Coalesce(outputType as any, parsedArgs); } evaluate(ctx: EvaluationContext) { @@ -76,7 +78,6 @@ export class Coalesce implements Expression { } outputDefined(): boolean { - return this.args.every(arg => arg.outputDefined()); + return this.args.every((arg) => arg.outputDefined()); } } - diff --git a/src/expression/definitions/coercion.ts b/src/expression/definitions/coercion.ts index 65b2d7b56..024eb0a38 100644 --- a/src/expression/definitions/coercion.ts +++ b/src/expression/definitions/coercion.ts @@ -35,15 +35,14 @@ export class Coercion implements Expression { constructor(type: Type, args: Array) { this.type = type; this.args = args; - } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - if (args.length < 2) - return context.error('Expected at least one argument.') as null; + if (args.length < 2) return context.error('Expected at least one argument.') as null; - const name: string = (args[0] as any); - if (!types[name]) throw new Error(`Can't parse ${name} as it is not part of the known types`); + const name: string = args[0] as any; + if (!types[name]) + throw new Error(`Can't parse ${name} as it is not part of the known types`); if ((name === 'to-boolean' || name === 'to-string') && args.length !== 2) return context.error('Expected one argument.') as null; @@ -81,11 +80,19 @@ export class Coercion implements Expression { error = validateRGBA(input[0], input[1], input[2], input[3]); } if (!error) { - return new Color((input[0] as any) / 255, (input[1] as any) / 255, (input[2] as any) / 255, (input[3] as any)); + return new Color( + (input[0] as any) / 255, + (input[1] as any) / 255, + (input[2] as any) / 255, + input[3] as any + ); } } } - throw new RuntimeError(error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); + throw new RuntimeError( + error || + `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'` + ); } case 'padding': { let input; @@ -97,7 +104,9 @@ export class Coercion implements Expression { return pad; } } - throw new RuntimeError(`Could not parse padding from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); + throw new RuntimeError( + `Could not parse padding from value '${typeof input === 'string' ? input : JSON.stringify(input)}'` + ); } case 'numberArray': { let input; @@ -109,7 +118,9 @@ export class Coercion implements Expression { return val; } } - throw new RuntimeError(`Could not parse numberArray from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); + throw new RuntimeError( + `Could not parse numberArray from value '${typeof input === 'string' ? input : JSON.stringify(input)}'` + ); } case 'colorArray': { let input; @@ -121,7 +132,9 @@ export class Coercion implements Expression { return val; } } - throw new RuntimeError(`Could not parse colorArray from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); + throw new RuntimeError( + `Could not parse colorArray from value '${typeof input === 'string' ? input : JSON.stringify(input)}'` + ); } case 'variableAnchorOffsetCollection': { let input; @@ -133,7 +146,9 @@ export class Coercion implements Expression { return coll; } } - throw new RuntimeError(`Could not parse variableAnchorOffsetCollection from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`); + throw new RuntimeError( + `Could not parse variableAnchorOffsetCollection from value '${typeof input === 'string' ? input : JSON.stringify(input)}'` + ); } case 'number': { let value = null; @@ -164,7 +179,6 @@ export class Coercion implements Expression { } outputDefined(): boolean { - return this.args.every(arg => arg.outputDefined()); + return this.args.every((arg) => arg.outputDefined()); } } - diff --git a/src/expression/definitions/collator.ts b/src/expression/definitions/collator.ts index 050f87c58..ccd7f2a67 100644 --- a/src/expression/definitions/collator.ts +++ b/src/expression/definitions/collator.ts @@ -12,7 +12,11 @@ export class CollatorExpression implements Expression { diacriticSensitive: Expression; locale: Expression | null; - constructor(caseSensitive: Expression, diacriticSensitive: Expression, locale: Expression | null) { + constructor( + caseSensitive: Expression, + diacriticSensitive: Expression, + locale: Expression | null + ) { this.type = CollatorType; this.locale = locale; this.caseSensitive = caseSensitive; @@ -20,19 +24,24 @@ export class CollatorExpression implements Expression { } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - if (args.length !== 2) - return context.error('Expected one argument.') as null; + if (args.length !== 2) return context.error('Expected one argument.') as null; - const options = (args[1] as any); + const options = args[1] as any; if (typeof options !== 'object' || Array.isArray(options)) return context.error('Collator options argument must be an object.') as null; const caseSensitive = context.parse( - options['case-sensitive'] === undefined ? false : options['case-sensitive'], 1, BooleanType); + options['case-sensitive'] === undefined ? false : options['case-sensitive'], + 1, + BooleanType + ); if (!caseSensitive) return null; const diacriticSensitive = context.parse( - options['diacritic-sensitive'] === undefined ? false : options['diacritic-sensitive'], 1, BooleanType); + options['diacritic-sensitive'] === undefined ? false : options['diacritic-sensitive'], + 1, + BooleanType + ); if (!diacriticSensitive) return null; let locale = null; @@ -45,7 +54,11 @@ export class CollatorExpression implements Expression { } evaluate(ctx: EvaluationContext) { - return new Collator(this.caseSensitive.evaluate(ctx), this.diacriticSensitive.evaluate(ctx), this.locale ? this.locale.evaluate(ctx) : null); + return new Collator( + this.caseSensitive.evaluate(ctx), + this.diacriticSensitive.evaluate(ctx), + this.locale ? this.locale.evaluate(ctx) : null + ); } eachChild(fn: (_: Expression) => void) { diff --git a/src/expression/definitions/comparison.ts b/src/expression/definitions/comparison.ts index de32d1352..9f0a30ce8 100644 --- a/src/expression/definitions/comparison.ts +++ b/src/expression/definitions/comparison.ts @@ -13,32 +13,56 @@ type ComparisonOperator = '==' | '!=' | '<' | '>' | '<=' | '>='; function isComparableType(op: ComparisonOperator, type: Type) { if (op === '==' || op === '!=') { // equality operator - return type.kind === 'boolean' || + return ( + type.kind === 'boolean' || type.kind === 'string' || type.kind === 'number' || type.kind === 'null' || - type.kind === 'value'; + type.kind === 'value' + ); } else { // ordering operator - return type.kind === 'string' || - type.kind === 'number' || - type.kind === 'value'; + return type.kind === 'string' || type.kind === 'number' || type.kind === 'value'; } } -function eq(ctx, a, b) { return a === b; } -function neq(ctx, a, b) { return a !== b; } -function lt(ctx, a, b) { return a < b; } -function gt(ctx, a, b) { return a > b; } -function lteq(ctx, a, b) { return a <= b; } -function gteq(ctx, a, b) { return a >= b; } +function eq(ctx, a, b) { + return a === b; +} +function neq(ctx, a, b) { + return a !== b; +} +function lt(ctx, a, b) { + return a < b; +} +function gt(ctx, a, b) { + return a > b; +} +function lteq(ctx, a, b) { + return a <= b; +} +function gteq(ctx, a, b) { + return a >= b; +} -function eqCollate(ctx, a, b, c) { return c.compare(a, b) === 0; } -function neqCollate(ctx, a, b, c) { return !eqCollate(ctx, a, b, c); } -function ltCollate(ctx, a, b, c) { return c.compare(a, b) < 0; } -function gtCollate(ctx, a, b, c) { return c.compare(a, b) > 0; } -function lteqCollate(ctx, a, b, c) { return c.compare(a, b) <= 0; } -function gteqCollate(ctx, a, b, c) { return c.compare(a, b) >= 0; } +function eqCollate(ctx, a, b, c) { + return c.compare(a, b) === 0; +} +function neqCollate(ctx, a, b, c) { + return !eqCollate(ctx, a, b, c); +} +function ltCollate(ctx, a, b, c) { + return c.compare(a, b) < 0; +} +function gtCollate(ctx, a, b, c) { + return c.compare(a, b) > 0; +} +function lteqCollate(ctx, a, b, c) { + return c.compare(a, b) <= 0; +} +function gteqCollate(ctx, a, b, c) { + return c.compare(a, b) >= 0; +} /** * Special form for comparison operators, implementing the signatures: @@ -79,17 +103,25 @@ function makeComparison(op: ComparisonOperator, compareBasic, compareWithCollato if (args.length !== 3 && args.length !== 4) return context.error('Expected two or three arguments.') as null; - const op: ComparisonOperator = (args[0] as any); + const op: ComparisonOperator = args[0] as any; let lhs = context.parse(args[1], 1, ValueType); if (!lhs) return null; if (!isComparableType(op, lhs.type)) { - return context.concat(1).error(`"${op}" comparisons are not supported for type '${typeToString(lhs.type)}'.`) as null; + return context + .concat(1) + .error( + `"${op}" comparisons are not supported for type '${typeToString(lhs.type)}'.` + ) as null; } let rhs = context.parse(args[2], 2, ValueType); if (!rhs) return null; if (!isComparableType(op, rhs.type)) { - return context.concat(2).error(`"${op}" comparisons are not supported for type '${typeToString(rhs.type)}'.`) as null; + return context + .concat(2) + .error( + `"${op}" comparisons are not supported for type '${typeToString(rhs.type)}'.` + ) as null; } if ( @@ -97,7 +129,9 @@ function makeComparison(op: ComparisonOperator, compareBasic, compareWithCollato lhs.type.kind !== 'value' && rhs.type.kind !== 'value' ) { - return context.error(`Cannot compare types '${typeToString(lhs.type)}' and '${typeToString(rhs.type)}'.`) as null; + return context.error( + `Cannot compare types '${typeToString(lhs.type)}' and '${typeToString(rhs.type)}'.` + ) as null; } if (isOrderComparison) { @@ -119,7 +153,9 @@ function makeComparison(op: ComparisonOperator, compareBasic, compareWithCollato lhs.type.kind !== 'value' && rhs.type.kind !== 'value' ) { - return context.error('Cannot use collator to compare non-string types.') as null; + return context.error( + 'Cannot use collator to compare non-string types.' + ) as null; } collator = context.parse(args[3], 3, CollatorType); if (!collator) return null; @@ -137,7 +173,9 @@ function makeComparison(op: ComparisonOperator, compareBasic, compareWithCollato const rt = typeOf(rhs); // check that type is string or number, and equal if (lt.kind !== rt.kind || !(lt.kind === 'string' || lt.kind === 'number')) { - throw new RuntimeError(`Expected arguments for "${op}" to be (string, string) or (number, number), but found (${lt.kind}, ${rt.kind}) instead.`); + throw new RuntimeError( + `Expected arguments for "${op}" to be (string, string) or (number, number), but found (${lt.kind}, ${rt.kind}) instead.` + ); } } @@ -149,9 +187,9 @@ function makeComparison(op: ComparisonOperator, compareBasic, compareWithCollato } } - return this.collator ? - compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx)) : - compareBasic(ctx, lhs, rhs); + return this.collator + ? compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx)) + : compareBasic(ctx, lhs, rhs); } eachChild(fn: (_: Expression) => void) { diff --git a/src/expression/definitions/distance.ts b/src/expression/definitions/distance.ts index 3d1f6d107..c4fff08ca 100644 --- a/src/expression/definitions/distance.ts +++ b/src/expression/definitions/distance.ts @@ -4,7 +4,14 @@ import {ParsingContext} from '../parsing_context'; import {NumberType, Type} from '../types'; import {isValue} from '../values'; import {EvaluationContext} from '../evaluation_context'; -import {BBox, boxWithinBox, getLngLatFromTileCoord, pointWithinPolygon, segmentIntersectSegment, updateBBox} from '../../util/geometry_util'; +import { + BBox, + boxWithinBox, + getLngLatFromTileCoord, + pointWithinPolygon, + segmentIntersectSegment, + updateBBox +} from '../../util/geometry_util'; import {classifyRings} from '../../util/classify_rings'; import {CheapRuler} from '../../util/cheap_ruler'; @@ -38,15 +45,19 @@ function splitRange(range: IndexRange, isLine: boolean): [IndexRange, IndexRange return [range, null]; } const size1 = Math.floor(size / 2); - return [[range[0], range[0] + size1], - [range[0] + size1, range[1]]]; + return [ + [range[0], range[0] + size1], + [range[0] + size1, range[1]] + ]; } if (size === 1) { return [range, null]; } const size1 = Math.floor(size / 2) - 1; - return [[range[0], range[0] + size1], - [range[0] + size1 + 1, range[1]]]; + return [ + [range[0], range[0] + size1], + [range[0] + size1 + 1, range[1]] + ]; } function getBBox(coords: [number, number][], range: IndexRange): BBox { @@ -72,7 +83,12 @@ function getPolygonBBox(polygon: [number, number][][]): BBox { } function isValidBBox(bbox: BBox): boolean { - return bbox[0] !== -Infinity && bbox[1] !== -Infinity && bbox[2] !== Infinity && bbox[3] !== Infinity; + return ( + bbox[0] !== -Infinity && + bbox[1] !== -Infinity && + bbox[2] !== Infinity && + bbox[3] !== Infinity + ); } // Calculate the distance between two bounding boxes. @@ -103,23 +119,40 @@ function bboxToBBoxDistance(bbox1: BBox, bbox2: BBox, ruler: CheapRuler): number return ruler.distance([0.0, 0.0], [dx, dy]); } -function pointToLineDistance(point: [number, number], line: [number, number][], ruler: CheapRuler): number { +function pointToLineDistance( + point: [number, number], + line: [number, number][], + ruler: CheapRuler +): number { const nearestPoint = ruler.pointOnLine(line, point); return ruler.distance(point, nearestPoint.point); } -function segmentToSegmentDistance(p1: [number, number], p2: [number, number], - q1: [number, number], q2: [number, number], ruler: CheapRuler): number { - const dist1 = Math.min(pointToLineDistance(p1, [q1, q2], ruler), pointToLineDistance(p2, [q1, q2], ruler)); - const dist2 = Math.min(pointToLineDistance(q1, [p1, p2], ruler), pointToLineDistance(q2, [p1, p2], ruler)); +function segmentToSegmentDistance( + p1: [number, number], + p2: [number, number], + q1: [number, number], + q2: [number, number], + ruler: CheapRuler +): number { + const dist1 = Math.min( + pointToLineDistance(p1, [q1, q2], ruler), + pointToLineDistance(p2, [q1, q2], ruler) + ); + const dist2 = Math.min( + pointToLineDistance(q1, [p1, p2], ruler), + pointToLineDistance(q2, [p1, p2], ruler) + ); return Math.min(dist1, dist2); } -function lineToLineDistance(line1: [number, number][], +function lineToLineDistance( + line1: [number, number][], range1: IndexRange, line2: [number, number][], range2: IndexRange, - ruler: CheapRuler): number { + ruler: CheapRuler +): number { const rangeSafe = isRangeSafe(range1, line1.length) && isRangeSafe(range2, line2.length); if (!rangeSafe) { return Infinity; @@ -141,11 +174,13 @@ function lineToLineDistance(line1: [number, number][], return dist; } -function pointsToPointsDistance(points1: [number, number][], +function pointsToPointsDistance( + points1: [number, number][], range1: IndexRange, points2: [number, number][], range2: IndexRange, - ruler: CheapRuler): number { + ruler: CheapRuler +): number { const rangeSafe = isRangeSafe(range1, points1.length) && isRangeSafe(range2, points2.length); if (!rangeSafe) { return NaN; @@ -163,9 +198,11 @@ function pointsToPointsDistance(points1: [number, number][], return dist; } -function pointToPolygonDistance(point: [number, number], +function pointToPolygonDistance( + point: [number, number], polygon: [number, number][][], - ruler: CheapRuler): number { + ruler: CheapRuler +): number { if (pointWithinPolygon(point, polygon, true)) { return 0.0; } @@ -188,10 +225,12 @@ function pointToPolygonDistance(point: [number, number], return dist; } -function lineToPolygonDistance(line: [number, number][], +function lineToPolygonDistance( + line: [number, number][], range: IndexRange, polygon: [number, number][][], - ruler: CheapRuler): number { + ruler: CheapRuler +): number { if (!isRangeSafe(range, line.length)) { return NaN; } @@ -231,13 +270,18 @@ function polygonIntersect(poly1: [number, number][][], poly2: [number, number][] return false; } -function polygonToPolygonDistance(polygon1: [number, number][][], +function polygonToPolygonDistance( + polygon1: [number, number][][], polygon2: [number, number][][], ruler, - currentMiniDist = Infinity): number { + currentMiniDist = Infinity +): number { const bbox1 = getPolygonBBox(polygon1); const bbox2 = getPolygonBBox(polygon2); - if (currentMiniDist !== Infinity && bboxToBBoxDistance(bbox1, bbox2, ruler) >= currentMiniDist) { + if ( + currentMiniDist !== Infinity && + bboxToBBoxDistance(bbox1, bbox2, ruler) >= currentMiniDist + ) { return currentMiniDist; } @@ -269,7 +313,14 @@ function polygonToPolygonDistance(polygon1: [number, number][][], return dist; } -function updateQueue(distQueue: TinyQueue, miniDist: number, ruler: CheapRuler, points: [number, number][], polyBBox: BBox, rangeA?: IndexRange) { +function updateQueue( + distQueue: TinyQueue, + miniDist: number, + ruler: CheapRuler, + points: [number, number][], + polyBBox: BBox, + rangeA?: IndexRange +) { if (!rangeA) { return; } @@ -281,13 +332,23 @@ function updateQueue(distQueue: TinyQueue, miniDist: number, ruler: Ch } } -function updateQueueTwoSets(distQueue: TinyQueue, miniDist: number, ruler: CheapRuler, - pointSet1: [number, number][], pointSet2: [number, number][], range1?: IndexRange, range2?: IndexRange) { +function updateQueueTwoSets( + distQueue: TinyQueue, + miniDist: number, + ruler: CheapRuler, + pointSet1: [number, number][], + pointSet2: [number, number][], + range1?: IndexRange, + range2?: IndexRange +) { if (!range1 || !range2) { return; } const tempDist = bboxToBBoxDistance( - getBBox(pointSet1, range1), getBBox(pointSet2, range2), ruler); + getBBox(pointSet1, range1), + getBBox(pointSet2, range2), + ruler + ); // Insert new pair to the queue if the bbox distance is less than // miniDist, The pair with biggest distance will be at the top if (tempDist < miniDist) { @@ -297,17 +358,22 @@ function updateQueueTwoSets(distQueue: TinyQueue, miniDist: number, r // Divide and conquer, the time complexity is O(n*lgn), faster than Brute force // O(n*n) Most of the time, use index for in-place processing. -function pointsToPolygonDistance(points: [number, number][], +function pointsToPolygonDistance( + points: [number, number][], isLine: boolean, polygon: [number, number][][], ruler: CheapRuler, - currentMiniDist = Infinity) { + currentMiniDist = Infinity +) { let miniDist = Math.min(ruler.distance(points[0], polygon[0][0]), currentMiniDist); if (miniDist === 0.0) { return miniDist; } - const distQueue = new TinyQueue([[0, [0, points.length - 1], [0, 0]]], compareDistPair); + const distQueue = new TinyQueue( + [[0, [0, points.length - 1], [0, 0]]], + compareDistPair + ); const polyBBox = getPolygonBBox(polygon); while (distQueue.length > 0) { @@ -349,18 +415,23 @@ function pointsToPolygonDistance(points: [number, number][], return miniDist; } -function pointSetToPointSetDistance(pointSet1: [number, number][], +function pointSetToPointSetDistance( + pointSet1: [number, number][], isLine1: boolean, pointSet2: [number, number][], isLine2: boolean, ruler: CheapRuler, - currentMiniDist = Infinity): number { + currentMiniDist = Infinity +): number { let miniDist = Math.min(currentMiniDist, ruler.distance(pointSet1[0], pointSet2[0])); if (miniDist === 0.0) { return miniDist; } - const distQueue = new TinyQueue([[0, [0, pointSet1.length - 1], [0, pointSet2.length - 1]]], compareDistPair); + const distQueue = new TinyQueue( + [[0, [0, pointSet1.length - 1], [0, pointSet2.length - 1]]], + compareDistPair + ); while (distQueue.length > 0) { const distPair = distQueue.pop(); @@ -407,10 +478,42 @@ function pointSetToPointSetDistance(pointSet1: [number, number][], } else { const newRangesA = splitRange(rangeA, isLine1); const newRangesB = splitRange(rangeB, isLine2); - updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[0], newRangesB[0]); - updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[0], newRangesB[1]); - updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[1], newRangesB[0]); - updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[1], newRangesB[1]); + updateQueueTwoSets( + distQueue, + miniDist, + ruler, + pointSet1, + pointSet2, + newRangesA[0], + newRangesB[0] + ); + updateQueueTwoSets( + distQueue, + miniDist, + ruler, + pointSet1, + pointSet2, + newRangesA[0], + newRangesB[1] + ); + updateQueueTwoSets( + distQueue, + miniDist, + ruler, + pointSet1, + pointSet2, + newRangesA[1], + newRangesB[0] + ); + updateQueueTwoSets( + distQueue, + miniDist, + ruler, + pointSet1, + pointSet2, + newRangesA[1], + newRangesB[1] + ); } } return miniDist; @@ -418,7 +521,9 @@ function pointSetToPointSetDistance(pointSet1: [number, number][], function pointToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeometry[]) { const tilePoints = ctx.geometry(); - const pointPosition = tilePoints.flat().map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical) as [number, number]); + const pointPosition = tilePoints + .flat() + .map((p) => getLngLatFromTileCoord([p.x, p.y], ctx.canonical) as [number, number]); if (tilePoints.length === 0) { return NaN; } @@ -427,13 +532,42 @@ function pointToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeome for (const geometry of geometries) { switch (geometry.type) { case 'Point': - dist = Math.min(dist, pointSetToPointSetDistance(pointPosition, false, [geometry.coordinates as [number, number]], false, ruler, dist)); + dist = Math.min( + dist, + pointSetToPointSetDistance( + pointPosition, + false, + [geometry.coordinates as [number, number]], + false, + ruler, + dist + ) + ); break; case 'LineString': - dist = Math.min(dist, pointSetToPointSetDistance(pointPosition, false, geometry.coordinates as [number, number][], true, ruler, dist)); + dist = Math.min( + dist, + pointSetToPointSetDistance( + pointPosition, + false, + geometry.coordinates as [number, number][], + true, + ruler, + dist + ) + ); break; case 'Polygon': - dist = Math.min(dist, pointsToPolygonDistance(pointPosition, false, geometry.coordinates as [number, number][][], ruler, dist)); + dist = Math.min( + dist, + pointsToPolygonDistance( + pointPosition, + false, + geometry.coordinates as [number, number][][], + ruler, + dist + ) + ); break; } if (dist === 0.0) { @@ -445,7 +579,9 @@ function pointToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeome function lineStringToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeometry[]) { const tileLine = ctx.geometry(); - const linePositions = tileLine.flat().map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical) as [number, number]); + const linePositions = tileLine + .flat() + .map((p) => getLngLatFromTileCoord([p.x, p.y], ctx.canonical) as [number, number]); if (tileLine.length === 0) { return NaN; } @@ -454,13 +590,42 @@ function lineStringToGeometryDistance(ctx: EvaluationContext, geometries: Simple for (const geometry of geometries) { switch (geometry.type) { case 'Point': - dist = Math.min(dist, pointSetToPointSetDistance(linePositions, true, [geometry.coordinates as [number, number]], false, ruler, dist)); + dist = Math.min( + dist, + pointSetToPointSetDistance( + linePositions, + true, + [geometry.coordinates as [number, number]], + false, + ruler, + dist + ) + ); break; case 'LineString': - dist = Math.min(dist, pointSetToPointSetDistance(linePositions, true, geometry.coordinates as [number, number][], true, ruler, dist)); + dist = Math.min( + dist, + pointSetToPointSetDistance( + linePositions, + true, + geometry.coordinates as [number, number][], + true, + ruler, + dist + ) + ); break; case 'Polygon': - dist = Math.min(dist, pointsToPolygonDistance(linePositions, true, geometry.coordinates as [number, number][][], ruler, dist)); + dist = Math.min( + dist, + pointsToPolygonDistance( + linePositions, + true, + geometry.coordinates as [number, number][][], + ruler, + dist + ) + ); break; } if (dist === 0.0) { @@ -475,9 +640,11 @@ function polygonToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeo if (tilePolygon.length === 0 || tilePolygon[0].length === 0) { return NaN; } - const polygons = classifyRings(tilePolygon, 0).map(polygon => { - return polygon.map(ring => { - return ring.map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical) as [number, number]); + const polygons = classifyRings(tilePolygon, 0).map((polygon) => { + return polygon.map((ring) => { + return ring.map( + (p) => getLngLatFromTileCoord([p.x, p.y], ctx.canonical) as [number, number] + ); }); }); const ruler = new CheapRuler(polygons[0][0][0][1]); @@ -486,13 +653,39 @@ function polygonToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeo for (const polygon of polygons) { switch (geometry.type) { case 'Point': - dist = Math.min(dist, pointsToPolygonDistance([geometry.coordinates as [number, number]], false, polygon, ruler, dist)); + dist = Math.min( + dist, + pointsToPolygonDistance( + [geometry.coordinates as [number, number]], + false, + polygon, + ruler, + dist + ) + ); break; case 'LineString': - dist = Math.min(dist, pointsToPolygonDistance(geometry.coordinates as [number, number][], true, polygon, ruler, dist)); + dist = Math.min( + dist, + pointsToPolygonDistance( + geometry.coordinates as [number, number][], + true, + polygon, + ruler, + dist + ) + ); break; case 'Polygon': - dist = Math.min(dist, polygonToPolygonDistance(polygon, geometry.coordinates as [number, number][][], ruler, dist)); + dist = Math.min( + dist, + polygonToPolygonDistance( + polygon, + geometry.coordinates as [number, number][][], + ruler, + dist + ) + ); break; } if (dist === 0.0) { @@ -501,12 +694,13 @@ function polygonToGeometryDistance(ctx: EvaluationContext, geometries: SimpleGeo } } return dist; - } -function toSimpleGeometry(geometry: Exclude): SimpleGeometry[] { +function toSimpleGeometry( + geometry: Exclude +): SimpleGeometry[] { if (geometry.type === 'MultiPolygon') { - return geometry.coordinates.map(polygon => { + return geometry.coordinates.map((polygon) => { return { type: 'Polygon', coordinates: polygon @@ -514,7 +708,7 @@ function toSimpleGeometry(geometry: Exclude { + return geometry.coordinates.map((lineString) => { return { type: 'LineString', coordinates: lineString @@ -522,7 +716,7 @@ function toSimpleGeometry(geometry: Exclude { + return geometry.coordinates.map((point) => { return { type: 'Point', coordinates: point @@ -545,18 +739,25 @@ export class Distance implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 2) - return context.error(`'distance' expression requires exactly one argument, but found ${args.length - 1} instead.`) as null; + return context.error( + `'distance' expression requires exactly one argument, but found ${args.length - 1} instead.` + ) as null; if (isValue(args[1])) { - const geojson = (args[1] as any); + const geojson = args[1] as any; if (geojson.type === 'FeatureCollection') { - return new Distance(geojson, geojson.features.map(feature => toSimpleGeometry(feature.geometry)).flat()); + return new Distance( + geojson, + geojson.features.map((feature) => toSimpleGeometry(feature.geometry)).flat() + ); } else if (geojson.type === 'Feature') { return new Distance(geojson, toSimpleGeometry(geojson.geometry)); } else if ('type' in geojson && 'coordinates' in geojson) { return new Distance(geojson, toSimpleGeometry(geojson)); } } - return context.error('\'distance\' expression requires valid geojson object that contains polygon geometry type.') as null; + return context.error( + "'distance' expression requires valid geojson object that contains polygon geometry type." + ) as null; } evaluate(ctx: EvaluationContext) { diff --git a/src/expression/definitions/format.ts b/src/expression/definitions/format.ts index 038813fb2..9a76de05d 100644 --- a/src/expression/definitions/format.ts +++ b/src/expression/definitions/format.ts @@ -5,9 +5,14 @@ import { array, StringType, ColorType, - ResolvedImageType, + ResolvedImageType } from '../types'; -import {Formatted, FormattedSection, VERTICAL_ALIGN_OPTIONS, VerticalAlign} from '../types/formatted'; +import { + Formatted, + FormattedSection, + VERTICAL_ALIGN_OPTIONS, + VerticalAlign +} from '../types/formatted'; import {valueToString, typeOf} from '../values'; import type {Expression} from '../expression'; @@ -40,14 +45,14 @@ export class FormatExpression implements Expression { } const firstArg = args[1]; - if (!Array.isArray(firstArg) && typeof firstArg === 'object') { + if (!Array.isArray(firstArg) && typeof firstArg === 'object') { return context.error('First argument must be an image or text section.') as null; } const sections: Array = []; let nextTokenMayBeObject = false; for (let i = 1; i <= args.length - 1; ++i) { - const arg = (args[i] as any); + const arg = args[i] as any; if (nextTokenMayBeObject && typeof arg === 'object' && !Array.isArray(arg)) { nextTokenMayBeObject = false; @@ -72,8 +77,13 @@ export class FormatExpression implements Expression { let verticalAlign = null; if (arg['vertical-align']) { - if (typeof arg['vertical-align'] === 'string' && !VERTICAL_ALIGN_OPTIONS.includes(arg['vertical-align'] as VerticalAlign)) { - return context.error(`'vertical-align' must be one of: 'bottom', 'center', 'top' but found '${arg['vertical-align']}' instead.`) as null; + if ( + typeof arg['vertical-align'] === 'string' && + !VERTICAL_ALIGN_OPTIONS.includes(arg['vertical-align'] as VerticalAlign) + ) { + return context.error( + `'vertical-align' must be one of: 'bottom', 'center', 'top' but found '${arg['vertical-align']}' instead.` + ) as null; } verticalAlign = context.parse(arg['vertical-align'], 1, StringType); @@ -90,11 +100,24 @@ export class FormatExpression implements Expression { if (!content) return null; const kind = content.type.kind; - if (kind !== 'string' && kind !== 'value' && kind !== 'null' && kind !== 'resolvedImage') - return context.error('Formatted text type must be \'string\', \'value\', \'image\' or \'null\'.') as null; + if ( + kind !== 'string' && + kind !== 'value' && + kind !== 'null' && + kind !== 'resolvedImage' + ) + return context.error( + "Formatted text type must be 'string', 'value', 'image' or 'null'." + ) as null; nextTokenMayBeObject = true; - sections.push({content, scale: null, font: null, textColor: null, verticalAlign: null}); + sections.push({ + content, + scale: null, + font: null, + textColor: null, + verticalAlign: null + }); } } @@ -102,7 +125,7 @@ export class FormatExpression implements Expression { } evaluate(ctx: EvaluationContext) { - const evaluateSection = section => { + const evaluateSection = (section) => { const evaluatedContent = section.content.evaluate(ctx); if (typeOf(evaluatedContent) === ResolvedImageType) { return new FormattedSection( diff --git a/src/expression/definitions/global_state.ts b/src/expression/definitions/global_state.ts index fa44749aa..b3d9e6ec3 100644 --- a/src/expression/definitions/global_state.ts +++ b/src/expression/definitions/global_state.ts @@ -15,7 +15,9 @@ export class GlobalState implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 2) { - return context.error(`Expected 1 argument, but found ${args.length - 1} instead.`) as null; + return context.error( + `Expected 1 argument, but found ${args.length - 1} instead.` + ) as null; } const key = args[1]; @@ -25,7 +27,9 @@ export class GlobalState implements Expression { } if (typeof key !== 'string') { - return context.error(`Global state property must be string, but found ${typeof args[1]} instead.`) as null; + return context.error( + `Global state property must be string, but found ${typeof args[1]} instead.` + ) as null; } return new GlobalState(key); diff --git a/src/expression/definitions/image.ts b/src/expression/definitions/image.ts index 6ddecaf32..8a6534d61 100644 --- a/src/expression/definitions/image.ts +++ b/src/expression/definitions/image.ts @@ -30,7 +30,8 @@ export class ImageExpression implements Expression { const evaluatedImageName = this.input.evaluate(ctx); const value = ResolvedImage.fromString(evaluatedImageName); - if (value && ctx.availableImages) value.available = ctx.availableImages.indexOf(evaluatedImageName) > -1; + if (value && ctx.availableImages) + value.available = ctx.availableImages.indexOf(evaluatedImageName) > -1; return value; } diff --git a/src/expression/definitions/in.ts b/src/expression/definitions/in.ts index ff73a6dc4..dcc8447f6 100644 --- a/src/expression/definitions/in.ts +++ b/src/expression/definitions/in.ts @@ -6,7 +6,7 @@ import { typeToString, NumberType, isValidType, - isValidNativeType, + isValidNativeType } from '../types'; import {RuntimeError} from '../runtime_error'; import {typeOf} from '../values'; @@ -29,7 +29,9 @@ export class In implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 3) { - return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`) as null; + return context.error( + `Expected 2 arguments, but found ${args.length - 1} instead.` + ) as null; } const needle = context.parse(args[1], 1, ValueType); @@ -39,24 +41,30 @@ export class In implements Expression { if (!needle || !haystack) return null; if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) { - return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(needle.type)} instead`) as null; + return context.error( + `Expected first argument to be of type boolean, string, number or null, but found ${typeToString(needle.type)} instead` + ) as null; } return new In(needle, haystack); } evaluate(ctx: EvaluationContext) { - const needle = (this.needle.evaluate(ctx) as any); - const haystack = (this.haystack.evaluate(ctx) as any); + const needle = this.needle.evaluate(ctx) as any; + const haystack = this.haystack.evaluate(ctx) as any; if (!haystack) return false; if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) { - throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.`); + throw new RuntimeError( + `Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.` + ); } if (!isValidNativeType(haystack, ['string', 'array'])) { - throw new RuntimeError(`Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.`); + throw new RuntimeError( + `Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.` + ); } return haystack.indexOf(needle) >= 0; @@ -71,4 +79,3 @@ export class In implements Expression { return true; } } - diff --git a/src/expression/definitions/index.ts b/src/expression/definitions/index.ts index c061f8db5..23920fd74 100644 --- a/src/expression/definitions/index.ts +++ b/src/expression/definitions/index.ts @@ -1,4 +1,3 @@ - import {Let} from './let'; import {Var} from './var'; import {Literal} from './literal'; @@ -40,35 +39,35 @@ export const expressions: ExpressionRegistry = { '<': LessThan, '>=': GreaterThanOrEqual, '<=': LessThanOrEqual, - 'array': Assertion, - 'at': At, - 'boolean': Assertion, - 'case': Case, - 'coalesce': Coalesce, - 'collator': CollatorExpression, - 'format': FormatExpression, - 'image': ImageExpression, - 'in': In, + array: Assertion, + at: At, + boolean: Assertion, + case: Case, + coalesce: Coalesce, + collator: CollatorExpression, + format: FormatExpression, + image: ImageExpression, + in: In, 'index-of': IndexOf, - 'interpolate': Interpolate, + interpolate: Interpolate, 'interpolate-hcl': Interpolate, 'interpolate-lab': Interpolate, - 'length': Length, - 'let': Let, - 'literal': Literal, - 'match': Match, - 'number': Assertion, + length: Length, + let: Let, + literal: Literal, + match: Match, + number: Assertion, 'number-format': NumberFormat, - 'object': Assertion, - 'slice': Slice, - 'step': Step, - 'string': Assertion, + object: Assertion, + slice: Slice, + step: Step, + string: Assertion, 'to-boolean': Coercion, 'to-color': Coercion, 'to-number': Coercion, 'to-string': Coercion, - 'var': Var, - 'within': Within, - 'distance': Distance, + var: Var, + within: Within, + distance: Distance, 'global-state': GlobalState }; diff --git a/src/expression/definitions/index_of.ts b/src/expression/definitions/index_of.ts index 8907fb29e..2e7b01208 100644 --- a/src/expression/definitions/index_of.ts +++ b/src/expression/definitions/index_of.ts @@ -6,7 +6,7 @@ import { typeToString, NumberType, isValidType, - isValidNativeType, + isValidNativeType } from '../types'; import {RuntimeError} from '../runtime_error'; import {typeOf} from '../values'; @@ -30,8 +30,10 @@ export class IndexOf implements Expression { } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - if (args.length <= 2 || args.length >= 5) { - return context.error(`Expected 2 or 3 arguments, but found ${args.length - 1} instead.`) as null; + if (args.length <= 2 || args.length >= 5) { + return context.error( + `Expected 2 or 3 arguments, but found ${args.length - 1} instead.` + ) as null; } const needle = context.parse(args[1], 1, ValueType); @@ -40,7 +42,9 @@ export class IndexOf implements Expression { if (!needle || !haystack) return null; if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) { - return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(needle.type)} instead`) as null; + return context.error( + `Expected first argument to be of type boolean, string, number or null, but found ${typeToString(needle.type)} instead` + ) as null; } if (args.length === 4) { @@ -53,16 +57,18 @@ export class IndexOf implements Expression { } evaluate(ctx: EvaluationContext) { - const needle = (this.needle.evaluate(ctx) as any); - const haystack = (this.haystack.evaluate(ctx) as any); + const needle = this.needle.evaluate(ctx) as any; + const haystack = this.haystack.evaluate(ctx) as any; if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) { - throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.`); + throw new RuntimeError( + `Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.` + ); } let fromIndex; if (this.fromIndex) { - fromIndex = (this.fromIndex.evaluate(ctx) as number); + fromIndex = this.fromIndex.evaluate(ctx) as number; } if (isValidNativeType(haystack, ['string'])) { @@ -76,7 +82,9 @@ export class IndexOf implements Expression { } else if (isValidNativeType(haystack, ['array'])) { return haystack.indexOf(needle, fromIndex); } else { - throw new RuntimeError(`Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.`); + throw new RuntimeError( + `Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.` + ); } } @@ -92,4 +100,3 @@ export class IndexOf implements Expression { return false; } } - diff --git a/src/expression/definitions/interpolate.ts b/src/expression/definitions/interpolate.ts index 583abdf2a..2ec3040fa 100644 --- a/src/expression/definitions/interpolate.ts +++ b/src/expression/definitions/interpolate.ts @@ -1,6 +1,24 @@ import UnitBezier from '@mapbox/unitbezier'; -import {array, ArrayType, ColorType, ColorTypeT, NumberType, NumberTypeT, PaddingType, PaddingTypeT, NumberArrayTypeT, ColorArrayTypeT, VariableAnchorOffsetCollectionType, VariableAnchorOffsetCollectionTypeT, typeToString, verifyType, ProjectionDefinitionType, ColorArrayType, NumberArrayType} from '../types'; +import { + array, + ArrayType, + ColorType, + ColorTypeT, + NumberType, + NumberTypeT, + PaddingType, + PaddingTypeT, + NumberArrayTypeT, + ColorArrayTypeT, + VariableAnchorOffsetCollectionType, + VariableAnchorOffsetCollectionTypeT, + typeToString, + verifyType, + ProjectionDefinitionType, + ColorArrayType, + NumberArrayType +} from '../types'; import {findStopLessThanOrEqualTo} from '../stops'; import {Color} from '../types/color'; import {interpolateArray, interpolateNumber} from '../../util/interpolate-primitives'; @@ -16,16 +34,27 @@ import type {ParsingContext} from '../parsing_context'; import type {EvaluationContext} from '../evaluation_context'; import type {ProjectionDefinitionTypeT, Type} from '../types'; -export type InterpolationType = { - name: 'linear'; -} | { - name: 'exponential'; - base: number; -} | { - name: 'cubic-bezier'; - controlPoints: [number, number, number, number]; -}; -type InterpolatedValueType = NumberTypeT | ColorTypeT | ProjectionDefinitionTypeT | PaddingTypeT | NumberArrayTypeT | ColorArrayTypeT | VariableAnchorOffsetCollectionTypeT | ArrayType; +export type InterpolationType = + | { + name: 'linear'; + } + | { + name: 'exponential'; + base: number; + } + | { + name: 'cubic-bezier'; + controlPoints: [number, number, number, number]; + }; +type InterpolatedValueType = + | NumberTypeT + | ColorTypeT + | ProjectionDefinitionTypeT + | PaddingTypeT + | NumberArrayTypeT + | ColorArrayTypeT + | VariableAnchorOffsetCollectionTypeT + | ArrayType; export class Interpolate implements Expression { type: InterpolatedValueType; @@ -35,7 +64,13 @@ export class Interpolate implements Expression { labels: Array; outputs: Array; - constructor(type: InterpolatedValueType, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, stops: Stops) { + constructor( + type: InterpolatedValueType, + operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', + interpolation: InterpolationType, + input: Expression, + stops: Stops + ) { this.type = type; this.operator = operator; this.interpolation = interpolation; @@ -49,7 +84,12 @@ export class Interpolate implements Expression { } } - static interpolationFactor(interpolation: InterpolationType, input: number, lower: number, upper: number) { + static interpolationFactor( + interpolation: InterpolationType, + input: number, + lower: number, + upper: number + ) { let t = 0; if (interpolation.name === 'exponential') { t = exponentialInterpolation(input, interpolation.base, lower, upper); @@ -75,7 +115,11 @@ export class Interpolate implements Expression { } else if (interpolation[0] === 'exponential') { const base = interpolation[1]; if (typeof base !== 'number') - return context.error('Exponential interpolation requires a numeric base.', 1, 1) as null; + return context.error( + 'Exponential interpolation requires a numeric base.', + 1, + 1 + ) as null; interpolation = { name: 'exponential', base @@ -84,21 +128,30 @@ export class Interpolate implements Expression { const controlPoints = interpolation.slice(1); if ( controlPoints.length !== 4 || - controlPoints.some(t => typeof t !== 'number' || t < 0 || t > 1) + controlPoints.some((t) => typeof t !== 'number' || t < 0 || t > 1) ) { - return context.error('Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.', 1) as null; + return context.error( + 'Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.', + 1 + ) as null; } interpolation = { name: 'cubic-bezier', - controlPoints: (controlPoints as any) + controlPoints: controlPoints as any }; } else { - return context.error(`Unknown interpolation type ${String(interpolation[0])}`, 1, 0) as null; + return context.error( + `Unknown interpolation type ${String(interpolation[0])}`, + 1, + 0 + ) as null; } if (args.length - 1 < 4) { - return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`) as null; + return context.error( + `Expected at least 4 arguments, but found only ${args.length - 1}.` + ) as null; } if ((args.length - 1) % 2 !== 0) { @@ -111,7 +164,10 @@ export class Interpolate implements Expression { const stops: Stops = []; let outputType: Type = null; - if ((operator === 'interpolate-hcl' || operator === 'interpolate-lab') && context.expectedType != ColorArrayType) { + if ( + (operator === 'interpolate-hcl' || operator === 'interpolate-lab') && + context.expectedType != ColorArrayType + ) { outputType = ColorType; } else if (context.expectedType && context.expectedType.kind !== 'value') { outputType = context.expectedType; @@ -125,11 +181,17 @@ export class Interpolate implements Expression { const valueKey = i + 4; if (typeof label !== 'number') { - return context.error('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey) as null; + return context.error( + 'Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.', + labelKey + ) as null; } if (stops.length && stops[stops.length - 1][0] >= label) { - return context.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.', labelKey) as null; + return context.error( + 'Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.', + labelKey + ) as null; } const parsed = context.parse(value, valueKey, outputType); if (!parsed) return null; @@ -137,7 +199,8 @@ export class Interpolate implements Expression { stops.push([label, parsed]); } - if (!verifyType(outputType, NumberType) && + if ( + !verifyType(outputType, NumberType) && !verifyType(outputType, ProjectionDefinitionType) && !verifyType(outputType, ColorType) && !verifyType(outputType, PaddingType) && @@ -149,7 +212,13 @@ export class Interpolate implements Expression { return context.error(`Type ${typeToString(outputType)} is not interpolatable.`) as null; } - return new Interpolate(outputType, (operator as any), interpolation as InterpolationType, input as Expression, stops); + return new Interpolate( + outputType, + operator as any, + interpolation as InterpolationType, + input as Expression, + stops + ); } evaluate(ctx: EvaluationContext) { @@ -182,7 +251,7 @@ export class Interpolate implements Expression { case 'interpolate': switch (this.type.kind) { case 'number': - return interpolateNumber(outputLower, outputUpper, t) + return interpolateNumber(outputLower, outputUpper, t); case 'color': return Color.interpolate(outputLower, outputUpper, t); case 'padding': @@ -192,7 +261,11 @@ export class Interpolate implements Expression { case 'numberArray': return NumberArray.interpolate(outputLower, outputUpper, t); case 'variableAnchorOffsetCollection': - return VariableAnchorOffsetCollection.interpolate(outputLower, outputUpper, t); + return VariableAnchorOffsetCollection.interpolate( + outputLower, + outputUpper, + t + ); case 'array': return interpolateArray(outputLower, outputUpper, t); case 'projectionDefinition': @@ -223,7 +296,7 @@ export class Interpolate implements Expression { } outputDefined(): boolean { - return this.outputs.every(out => out.outputDefined()); + return this.outputs.every((out) => out.outputDefined()); } } @@ -261,7 +334,7 @@ export class Interpolate implements Expression { * expensive `Math.pow()` operations.) * * @private -*/ + */ function exponentialInterpolation(input, base, lowerValue, upperValue) { const difference = upperValue - lowerValue; const progress = input - lowerValue; @@ -283,4 +356,4 @@ export const interpolateFactory = { colorArray: ColorArray.interpolate, variableAnchorOffsetCollection: VariableAnchorOffsetCollection.interpolate, array: interpolateArray -} +}; diff --git a/src/expression/definitions/length.ts b/src/expression/definitions/length.ts index 198c739f6..693278b36 100644 --- a/src/expression/definitions/length.ts +++ b/src/expression/definitions/length.ts @@ -19,13 +19,21 @@ export class Length implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 2) - return context.error(`Expected 1 argument, but found ${args.length - 1} instead.`) as null; + return context.error( + `Expected 1 argument, but found ${args.length - 1} instead.` + ) as null; const input = context.parse(args[1], 1); if (!input) return null; - if (input.type.kind !== 'array' && input.type.kind !== 'string' && input.type.kind !== 'value') - return context.error(`Expected argument of type string or array, but found ${typeToString(input.type)} instead.`) as null; + if ( + input.type.kind !== 'array' && + input.type.kind !== 'string' && + input.type.kind !== 'value' + ) + return context.error( + `Expected argument of type string or array, but found ${typeToString(input.type)} instead.` + ) as null; return new Length(input); } @@ -38,7 +46,9 @@ export class Length implements Expression { } else if (Array.isArray(input)) { return input.length; } else { - throw new RuntimeError(`Expected value to be of type string or array, but found ${typeToString(typeOf(input))} instead.`); + throw new RuntimeError( + `Expected value to be of type string or array, but found ${typeToString(typeOf(input))} instead.` + ); } } @@ -50,4 +60,3 @@ export class Length implements Expression { return false; } } - diff --git a/src/expression/definitions/let.ts b/src/expression/definitions/let.ts index 7617bbec5..671cfecd3 100644 --- a/src/expression/definitions/let.ts +++ b/src/expression/definitions/let.ts @@ -1,7 +1,7 @@ import type {Type} from '../types'; import type {Expression} from '../expression'; import type {ParsingContext} from '../parsing_context'; -import type {EvaluationContext} from '../evaluation_context'; +import type {EvaluationContext} from '../evaluation_context'; export class Let implements Expression { type: Type; @@ -27,18 +27,26 @@ export class Let implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length < 4) - return context.error(`Expected at least 3 arguments, but found ${args.length - 1} instead.`) as null; + return context.error( + `Expected at least 3 arguments, but found ${args.length - 1} instead.` + ) as null; const bindings: Array<[string, Expression]> = []; for (let i = 1; i < args.length - 1; i += 2) { const name = args[i]; if (typeof name !== 'string') { - return context.error(`Expected string, but found ${typeof name} instead.`, i) as null; + return context.error( + `Expected string, but found ${typeof name} instead.`, + i + ) as null; } if (/[^a-zA-Z0-9_]/.test(name)) { - return context.error('Variable names must contain only alphanumeric characters or \'_\'.', i) as null; + return context.error( + "Variable names must contain only alphanumeric characters or '_'.", + i + ) as null; } const value = context.parse(args[i + 1], i + 1); @@ -47,7 +55,12 @@ export class Let implements Expression { bindings.push([name, value]); } - const result = context.parse(args[args.length - 1], args.length - 1, context.expectedType, bindings); + const result = context.parse( + args[args.length - 1], + args.length - 1, + context.expectedType, + bindings + ); if (!result) return null; return new Let(bindings, result); diff --git a/src/expression/definitions/literal.ts b/src/expression/definitions/literal.ts index 0e40f25ba..4e94d5b91 100644 --- a/src/expression/definitions/literal.ts +++ b/src/expression/definitions/literal.ts @@ -1,7 +1,7 @@ import {isValue, typeOf} from '../values'; import type {Type} from '../types'; -import type {Value} from '../values'; +import type {Value} from '../values'; import type {Expression} from '../expression'; import type {ParsingContext} from '../parsing_context'; @@ -16,12 +16,13 @@ export class Literal implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 2) - return context.error(`'literal' expression requires exactly one argument, but found ${args.length - 1} instead.`) as null; + return context.error( + `'literal' expression requires exactly one argument, but found ${args.length - 1} instead.` + ) as null; - if (!isValue(args[1])) - return context.error('invalid value') as null; + if (!isValue(args[1])) return context.error('invalid value') as null; - const value = (args[1] as any); + const value = args[1] as any; let type = typeOf(value); // special case: infer the item type if possible for zero-length arrays @@ -49,4 +50,3 @@ export class Literal implements Expression { return true; } } - diff --git a/src/expression/definitions/match.ts b/src/expression/definitions/match.ts index ae9bd5f86..37e75e9e9 100644 --- a/src/expression/definitions/match.ts +++ b/src/expression/definitions/match.ts @@ -1,4 +1,3 @@ - import {typeOf} from '../values'; import {ValueType} from '../types'; @@ -22,7 +21,14 @@ export class Match implements Expression { outputs: Array; otherwise: Expression; - constructor(inputType: Type, outputType: Type, input: Expression, cases: Cases, outputs: Array, otherwise: Expression) { + constructor( + inputType: Type, + outputType: Type, + input: Expression, + cases: Cases, + outputs: Array, + otherwise: Expression + ) { this.inputType = inputType; this.type = outputType; this.input = input; @@ -33,7 +39,9 @@ export class Match implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length < 5) - return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`) as null; + return context.error( + `Expected at least 4 arguments, but found only ${args.length - 1}.` + ) as null; if (args.length % 2 !== 1) return context.error('Expected an even number of arguments.') as null; @@ -61,11 +69,13 @@ export class Match implements Expression { if (typeof label !== 'number' && typeof label !== 'string') { return labelContext.error('Branch labels must be numbers or strings.') as null; } else if (typeof label === 'number' && Math.abs(label) > Number.MAX_SAFE_INTEGER) { - return labelContext.error(`Branch labels must be integers no larger than ${Number.MAX_SAFE_INTEGER}.`) as null; - + return labelContext.error( + `Branch labels must be integers no larger than ${Number.MAX_SAFE_INTEGER}.` + ) as null; } else if (typeof label === 'number' && Math.floor(label) !== label) { - return labelContext.error('Numeric branch labels must be integer values.') as null; - + return labelContext.error( + 'Numeric branch labels must be integer values.' + ) as null; } else if (!inputType) { inputType = typeOf(label); } else if (labelContext.checkSubtype(inputType, typeOf(label))) { @@ -91,16 +101,20 @@ export class Match implements Expression { const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType); if (!otherwise) return null; - if (input.type.kind !== 'value' && context.concat(1).checkSubtype(((inputType as any)), input.type)) { + if ( + input.type.kind !== 'value' && + context.concat(1).checkSubtype(inputType as any, input.type) + ) { return null; } - return new Match((inputType as any), (outputType as any), input, cases, outputs, otherwise); + return new Match(inputType as any, outputType as any, input, cases, outputs, otherwise); } evaluate(ctx: EvaluationContext) { - const input = (this.input.evaluate(ctx) as any); - const output = (typeOf(input) === this.inputType && this.outputs[this.cases[input]]) || this.otherwise; + const input = this.input.evaluate(ctx) as any; + const output = + (typeOf(input) === this.inputType && this.outputs[this.cases[input]]) || this.otherwise; return output.evaluate(ctx); } @@ -111,7 +125,6 @@ export class Match implements Expression { } outputDefined(): boolean { - return this.outputs.every(out => out.outputDefined()) && this.otherwise.outputDefined(); + return this.outputs.every((out) => out.outputDefined()) && this.otherwise.outputDefined(); } } - diff --git a/src/expression/definitions/number_format.ts b/src/expression/definitions/number_format.ts index 5ec6dfbae..41aada2d5 100644 --- a/src/expression/definitions/number_format.ts +++ b/src/expression/definitions/number_format.ts @@ -8,16 +8,18 @@ import type {Type} from '../types'; export class NumberFormat implements Expression { type: Type; number: Expression; - locale: Expression | null; // BCP 47 language tag + locale: Expression | null; // BCP 47 language tag currency: Expression | null; // ISO 4217 currency code, required if style=currency minFractionDigits: Expression | null; // Default 0 maxFractionDigits: Expression | null; // Default 3 - constructor(number: Expression, + constructor( + number: Expression, locale: Expression | null, currency: Expression | null, minFractionDigits: Expression | null, - maxFractionDigits: Expression | null) { + maxFractionDigits: Expression | null + ) { this.type = StringType; this.number = number; this.locale = locale; @@ -27,13 +29,12 @@ export class NumberFormat implements Expression { } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - if (args.length !== 3) - return context.error('Expected two arguments.') as null; + if (args.length !== 3) return context.error('Expected two arguments.') as null; const number = context.parse(args[1], 1, NumberType); if (!number) return null; - const options = (args[2] as any); + const options = args[2] as any; if (typeof options !== 'object' || Array.isArray(options)) return context.error('NumberFormat options argument must be an object.') as null; @@ -65,13 +66,16 @@ export class NumberFormat implements Expression { } evaluate(ctx: EvaluationContext) { - return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], - { - style: this.currency ? 'currency' : 'decimal', - currency: this.currency ? this.currency.evaluate(ctx) : undefined, - minimumFractionDigits: this.minFractionDigits ? this.minFractionDigits.evaluate(ctx) : undefined, - maximumFractionDigits: this.maxFractionDigits ? this.maxFractionDigits.evaluate(ctx) : undefined, - }).format(this.number.evaluate(ctx)); + return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], { + style: this.currency ? 'currency' : 'decimal', + currency: this.currency ? this.currency.evaluate(ctx) : undefined, + minimumFractionDigits: this.minFractionDigits + ? this.minFractionDigits.evaluate(ctx) + : undefined, + maximumFractionDigits: this.maxFractionDigits + ? this.maxFractionDigits.evaluate(ctx) + : undefined + }).format(this.number.evaluate(ctx)); } eachChild(fn: (_: Expression) => void) { diff --git a/src/expression/definitions/slice.ts b/src/expression/definitions/slice.ts index 100710994..e8b780579 100644 --- a/src/expression/definitions/slice.ts +++ b/src/expression/definitions/slice.ts @@ -5,7 +5,7 @@ import { array, typeToString, isValidType, - isValidNativeType, + isValidNativeType } from '../types'; import {RuntimeError} from '../runtime_error'; import {typeOf} from '../values'; @@ -26,12 +26,13 @@ export class Slice implements Expression { this.input = input; this.beginIndex = beginIndex; this.endIndex = endIndex; - } static parse(args: ReadonlyArray, context: ParsingContext): Expression { - if (args.length <= 2 || args.length >= 5) { - return context.error(`Expected 2 or 3 arguments, but found ${args.length - 1} instead.`) as null; + if (args.length <= 2 || args.length >= 5) { + return context.error( + `Expected 2 or 3 arguments, but found ${args.length - 1} instead.` + ) as null; } const input = context.parse(args[1], 1, ValueType); @@ -40,7 +41,9 @@ export class Slice implements Expression { if (!input || !beginIndex) return null; if (!isValidType(input.type, [array(ValueType), StringType, ValueType])) { - return context.error(`Expected first argument to be of type array or string, but found ${typeToString(input.type)} instead`) as null; + return context.error( + `Expected first argument to be of type array or string, but found ${typeToString(input.type)} instead` + ) as null; } if (args.length === 4) { @@ -53,12 +56,12 @@ export class Slice implements Expression { } evaluate(ctx: EvaluationContext) { - const input = (this.input.evaluate(ctx) as any); - const beginIndex = (this.beginIndex.evaluate(ctx) as number); + const input = this.input.evaluate(ctx) as any; + const beginIndex = this.beginIndex.evaluate(ctx) as number; let endIndex; if (this.endIndex) { - endIndex = (this.endIndex.evaluate(ctx) as number); + endIndex = this.endIndex.evaluate(ctx) as number; } if (isValidNativeType(input, ['string'])) { @@ -67,7 +70,9 @@ export class Slice implements Expression { } else if (isValidNativeType(input, ['array'])) { return input.slice(beginIndex, endIndex); } else { - throw new RuntimeError(`Expected first argument to be of type array or string, but found ${typeToString(typeOf(input))} instead.`); + throw new RuntimeError( + `Expected first argument to be of type array or string, but found ${typeToString(typeOf(input))} instead.` + ); } } @@ -83,4 +88,3 @@ export class Slice implements Expression { return false; } } - diff --git a/src/expression/definitions/step.ts b/src/expression/definitions/step.ts index f9c2c1b7e..3d1a5f603 100644 --- a/src/expression/definitions/step.ts +++ b/src/expression/definitions/step.ts @@ -29,7 +29,9 @@ export class Step implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length - 1 < 4) { - return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`) as null; + return context.error( + `Expected at least 4 arguments, but found only ${args.length - 1}.` + ) as null; } if ((args.length - 1) % 2 !== 0) { @@ -54,11 +56,17 @@ export class Step implements Expression { const valueKey = i + 1; if (typeof label !== 'number') { - return context.error('Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey) as null; + return context.error( + 'Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.', + labelKey + ) as null; } if (stops.length && stops[stops.length - 1][0] >= label) { - return context.error('Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.', labelKey) as null; + return context.error( + 'Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.', + labelKey + ) as null; } const parsed = context.parse(value, valueKey, outputType); @@ -78,7 +86,7 @@ export class Step implements Expression { return outputs[0].evaluate(ctx); } - const value = (this.input.evaluate(ctx) as any as number); + const value = this.input.evaluate(ctx) as any as number; if (value <= labels[0]) { return outputs[0].evaluate(ctx); } @@ -100,7 +108,6 @@ export class Step implements Expression { } outputDefined(): boolean { - return this.outputs.every(out => out.outputDefined()); + return this.outputs.every((out) => out.outputDefined()); } } - diff --git a/src/expression/definitions/var.ts b/src/expression/definitions/var.ts index 73a3d8647..2be7ef3a5 100644 --- a/src/expression/definitions/var.ts +++ b/src/expression/definitions/var.ts @@ -1,7 +1,7 @@ import type {Type} from '../types'; import type {Expression} from '../expression'; import type {ParsingContext} from '../parsing_context'; -import type {EvaluationContext} from '../evaluation_context'; +import type {EvaluationContext} from '../evaluation_context'; export class Var implements Expression { type: Type; @@ -16,11 +16,16 @@ export class Var implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 2 || typeof args[1] !== 'string') - return context.error('\'var\' expression requires exactly one string literal argument.') as null; + return context.error( + "'var' expression requires exactly one string literal argument." + ) as null; const name = args[1]; if (!context.scope.has(name)) { - return context.error(`Unknown variable "${name}". Make sure "${name}" has been bound in an enclosing "let" expression before using it.`, 1) as null; + return context.error( + `Unknown variable "${name}". Make sure "${name}" has been bound in an enclosing "let" expression before using it.`, + 1 + ) as null; } return new Var(name, context.scope.get(name)); @@ -36,4 +41,3 @@ export class Var implements Expression { return false; } } - diff --git a/src/expression/definitions/within.ts b/src/expression/definitions/within.ts index c46dec953..e19147b21 100644 --- a/src/expression/definitions/within.ts +++ b/src/expression/definitions/within.ts @@ -5,12 +5,26 @@ import type {Expression} from '../expression'; import type {ParsingContext} from '../parsing_context'; import type {EvaluationContext} from '../evaluation_context'; import {ICanonicalTileID} from '../../tiles_and_coordinates'; -import {BBox, EXTENT, boxWithinBox, getTileCoordinates, lineStringWithinPolygon, lineStringWithinPolygons, pointWithinPolygon, pointWithinPolygons, updateBBox} from '../../util/geometry_util'; +import { + BBox, + EXTENT, + boxWithinBox, + getTileCoordinates, + lineStringWithinPolygon, + lineStringWithinPolygons, + pointWithinPolygon, + pointWithinPolygons, + updateBBox +} from '../../util/geometry_util'; import {Point2D} from '../../point2d'; type GeoJSONPolygons = GeoJSON.Polygon | GeoJSON.MultiPolygon; -function getTilePolygon(coordinates: GeoJSON.Position[][], bbox: BBox, canonical: ICanonicalTileID) { +function getTilePolygon( + coordinates: GeoJSON.Position[][], + bbox: BBox, + canonical: ICanonicalTileID +) { const polygon = []; for (let i = 0; i < coordinates.length; i++) { const ring = []; @@ -24,7 +38,11 @@ function getTilePolygon(coordinates: GeoJSON.Position[][], bbox: BBox, canonical return polygon; } -function getTilePolygons(coordinates: GeoJSON.Position[][][], bbox: BBox, canonical: ICanonicalTileID) { +function getTilePolygons( + coordinates: GeoJSON.Position[][][], + bbox: BBox, + canonical: ICanonicalTileID +) { const polygons = []; for (let i = 0; i < coordinates.length; i++) { const polygon = getTilePolygon(coordinates[i], bbox, canonical); @@ -36,9 +54,19 @@ function getTilePolygons(coordinates: GeoJSON.Position[][][], bbox: BBox, canoni function updatePoint(p: GeoJSON.Position, bbox: BBox, polyBBox: BBox, worldSize: number) { if (p[0] < polyBBox[0] || p[0] > polyBBox[2]) { const halfWorldSize = worldSize * 0.5; - let shift = (p[0] - polyBBox[0] > halfWorldSize) ? -worldSize : (polyBBox[0] - p[0] > halfWorldSize) ? worldSize : 0; + let shift = + p[0] - polyBBox[0] > halfWorldSize + ? -worldSize + : polyBBox[0] - p[0] > halfWorldSize + ? worldSize + : 0; if (shift === 0) { - shift = (p[0] - polyBBox[2] > halfWorldSize) ? -worldSize : (polyBBox[2] - p[0] > halfWorldSize) ? worldSize : 0; + shift = + p[0] - polyBBox[2] > halfWorldSize + ? -worldSize + : polyBBox[2] - p[0] > halfWorldSize + ? worldSize + : 0; } p[0] += shift; } @@ -50,7 +78,12 @@ function resetBBox(bbox: BBox) { bbox[2] = bbox[3] = -Infinity; } -function getTilePoints(geometry: Point2D[][], pointBBox: BBox, polyBBox: BBox, canonical: ICanonicalTileID): [number, number][] { +function getTilePoints( + geometry: Point2D[][], + pointBBox: BBox, + polyBBox: BBox, + canonical: ICanonicalTileID +): [number, number][] { const worldSize = Math.pow(2, canonical.z) * EXTENT; const shifts = [canonical.x * EXTENT, canonical.y * EXTENT]; const tilePoints: [number, number][] = []; @@ -64,12 +97,17 @@ function getTilePoints(geometry: Point2D[][], pointBBox: BBox, polyBBox: BBox, c return tilePoints; } -function getTileLines(geometry: Point2D[][], lineBBox: BBox, polyBBox: BBox, canonical: ICanonicalTileID): [number, number][][] { +function getTileLines( + geometry: Point2D[][], + lineBBox: BBox, + polyBBox: BBox, + canonical: ICanonicalTileID +): [number, number][][] { const worldSize = Math.pow(2, canonical.z) * EXTENT; const shifts = [canonical.x * EXTENT, canonical.y * EXTENT]; const tileLines: [number, number][][] = []; for (const line of geometry) { - const tileLine:[number, number][] = []; + const tileLine: [number, number][] = []; for (const point of line) { const p: [number, number] = [point.x + shifts[0], point.y + shifts[1]]; updateBBox(lineBBox, p); @@ -156,9 +194,11 @@ export class Within implements Expression { static parse(args: ReadonlyArray, context: ParsingContext): Expression { if (args.length !== 2) - return context.error(`'within' expression requires exactly one argument, but found ${args.length - 1} instead.`) as null; + return context.error( + `'within' expression requires exactly one argument, but found ${args.length - 1} instead.` + ) as null; if (isValue(args[1])) { - const geojson = (args[1] as any); + const geojson = args[1] as any; if (geojson.type === 'FeatureCollection') { const polygonsCoords: GeoJSON.Position[][][] = []; for (const polygon of geojson.features) { @@ -177,17 +217,18 @@ export class Within implements Expression { }; return new Within(geojson, multipolygonWrapper); } - } else if (geojson.type === 'Feature') { const type = geojson.geometry.type; if (type === 'Polygon' || type === 'MultiPolygon') { return new Within(geojson, geojson.geometry); } - } else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') { + } else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') { return new Within(geojson, geojson); } } - return context.error('\'within\' expression requires valid geojson object that contains polygon geometry type.') as null; + return context.error( + "'within' expression requires valid geojson object that contains polygon geometry type." + ) as null; } evaluate(ctx: EvaluationContext) { diff --git a/src/expression/evaluation_context.ts b/src/expression/evaluation_context.ts index 6443961c1..b3cc7b299 100644 --- a/src/expression/evaluation_context.ts +++ b/src/expression/evaluation_context.ts @@ -30,7 +30,11 @@ export class EvaluationContext { } geometryType() { - return this.feature ? typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : this.feature.type : null; + return this.feature + ? typeof this.feature.type === 'number' + ? geometryTypes[this.feature.type] + : this.feature.type + : null; } geometry() { @@ -42,7 +46,7 @@ export class EvaluationContext { } properties() { - return this.feature && this.feature.properties || {}; + return (this.feature && this.feature.properties) || {}; } parseColor(input: string): Color { @@ -54,4 +58,3 @@ export class EvaluationContext { return cached; } } - diff --git a/src/expression/expression.test-d.ts b/src/expression/expression.test-d.ts index af2619740..940f03b7c 100644 --- a/src/expression/expression.test-d.ts +++ b/src/expression/expression.test-d.ts @@ -10,19 +10,33 @@ describe('Distance expression', () => { expectTypeOf<['distance', {type: 'Nope!'}]>().not.toExtend(); }); test('expression as geometry typecheck', () => { - expectTypeOf<['distance', ['literal', {type: 'MultiPoint'; coordinates: [[3, 3], [3, 4]]}]]>().not.toExtend(); + expectTypeOf< + ['distance', ['literal', {type: 'MultiPoint'; coordinates: [[3, 3], [3, 4]]}]] + >().not.toExtend(); }); }); describe('valid expression', () => { test('multi point geometry typecheck', () => { - expectTypeOf<['distance', {type: 'MultiPoint'; coordinates: [[3, 3], [3, 4]]}]>().toExtend(); + expectTypeOf< + ['distance', {type: 'MultiPoint'; coordinates: [[3, 3], [3, 4]]}] + >().toExtend(); }); test('multi line geometry typecheck', () => { - expectTypeOf<['distance', {type: 'MultiLineString'; coordinates: [[[3, 3], [3, 4]]]}]>().toExtend(); + expectTypeOf< + ['distance', {type: 'MultiLineString'; coordinates: [[[3, 3], [3, 4]]]}] + >().toExtend(); }); test('multi polygon geometry typecheck', () => { - expectTypeOf<['distance', {type: 'MultiPolygon'; coordinates: [[[[3, 3], [3, 4], [4, 4], [4, 3], [3, 3]]]]}]>().toExtend(); + expectTypeOf< + [ + 'distance', + { + type: 'MultiPolygon'; + coordinates: [[[[3, 3], [3, 4], [4, 4], [4, 3], [3, 3]]]]; + } + ] + >().toExtend(); }); }); }); @@ -35,45 +49,69 @@ describe('"array" expression', () => { expectTypeOf<['array', 'number', 3, [1, 2, 3]]>().not.toExtend(); expectTypeOf<['array', ['literal', []]]>().toExtend(); - expectTypeOf<['array', 'number', ['literal', [1, 2, 3]]]>().toExtend(); - expectTypeOf<['array', 'number', typeof Number.MAX_SAFE_INTEGER, ['get', 'arr']]>().toExtend(); + expectTypeOf< + ['array', 'number', ['literal', [1, 2, 3]]] + >().toExtend(); + expectTypeOf< + ['array', 'number', typeof Number.MAX_SAFE_INTEGER, ['get', 'arr']] + >().toExtend(); }); test('type requires either "string", "number", or "boolean" as the asserted type', () => { expectTypeOf<['array', 0, ['literal', []]]>().not.toExtend(); expectTypeOf<['array', '0', ['literal', []]]>().not.toExtend(); - expectTypeOf<['array', ['literal', 'number'], ['literal', []]]>().not.toExtend(); + expectTypeOf< + ['array', ['literal', 'number'], ['literal', []]] + >().not.toExtend(); expectTypeOf<['array', 'string', ['literal', []]]>().toExtend(); expectTypeOf<['array', 'number', ['literal', []]]>().toExtend(); expectTypeOf<['array', 'boolean', ['literal', []]]>().toExtend(); }); test('type requires a number literal as the asserted length', () => { - expectTypeOf<['array', 'string', '0', ['literal', []]]>().not.toExtend(); - expectTypeOf<['array', 'string', ['literal', 0], ['literal', []]]>().not.toExtend(); + expectTypeOf< + ['array', 'string', '0', ['literal', []]] + >().not.toExtend(); + expectTypeOf< + ['array', 'string', ['literal', 0], ['literal', []]] + >().not.toExtend(); expectTypeOf<['array', 'string', 0, ['literal', []]]>().toExtend(); - expectTypeOf<['array', 'string', 2, ['literal', ['one', 'two']]]>().toExtend(); + expectTypeOf< + ['array', 'string', 2, ['literal', ['one', 'two']]] + >().toExtend(); }); }); describe('"format" expression', () => { test('type rejects bare string arrays in the "text-font" style override', () => { - expectTypeOf<['format', 'foo', {'text-font': ['Helvetica', 'Arial']}]>().not.toExtend(); + expectTypeOf< + ['format', 'foo', {'text-font': ['Helvetica', 'Arial']}] + >().not.toExtend(); }); test('type accepts expression which scales text', () => { - expectTypeOf<['format', ['get', 'title'], {'font-scale': 0.8}]>().toExtend(); + expectTypeOf< + ['format', ['get', 'title'], {'font-scale': 0.8}] + >().toExtend(); }); test('type requires either "bottom", "center", or "top" as the vertical alignment', () => { - expectTypeOf<['format', 'foo', {'vertical-align': 'middle'}]>().not.toExtend(); + expectTypeOf< + ['format', 'foo', {'vertical-align': 'middle'}] + >().not.toExtend(); }); test('type accepts expression which aligns a text section vertically', () => { - expectTypeOf<['format', 'foo', {'vertical-align': 'top'}]>().toExtend(); + expectTypeOf< + ['format', 'foo', {'vertical-align': 'top'}] + >().toExtend(); }); test('type accepts expression which aligns an image vertically', () => { - expectTypeOf<['format', ['image', 'bar'], {'vertical-align': 'bottom'}]>().toExtend(); + expectTypeOf< + ['format', ['image', 'bar'], {'vertical-align': 'bottom'}] + >().toExtend(); }); test('type accepts expression which applies multiple style overrides', () => { - expectTypeOf<['format', 'foo', {'font-scale': 0.8; 'text-color': '#fff'}]>().toExtend(); + expectTypeOf< + ['format', 'foo', {'font-scale': 0.8; 'text-color': '#fff'}] + >().toExtend(); }); test('type accepts expression which applies default styles with an empty overrides object', () => { expectTypeOf<['format', ['downcase', 'BaR'], {}]>().toExtend(); @@ -104,7 +142,9 @@ describe('"typeof" expression', () => { expectTypeOf<['typeof', true]>().toExtend(); }); test('type accepts expression which returns a string describing the type of the given expression value', () => { - expectTypeOf<['typeof', ['concat', 'foo', ['to-string', 0]]]>().toExtend(); + expectTypeOf< + ['typeof', ['concat', 'foo', ['to-string', 0]]] + >().toExtend(); }); }); @@ -131,7 +171,9 @@ describe('"global-state" expression', () => { expectTypeOf<['global-state']>().not.toExtend(); }); test('type requires a string literal as the property argument', () => { - expectTypeOf<['global-state', ['concat', 'pr', 'op']]>().not.toExtend(); + expectTypeOf< + ['global-state', ['concat', 'pr', 'op']] + >().not.toExtend(); }); test('type rejects a second argument', () => { expectTypeOf<['global-state', 'foo', 'bar']>().not.toExtend(); @@ -173,13 +215,17 @@ describe('"in" expression', () => { expectTypeOf<['in', 'b', 'abc']>().toExtend(); }); test('type accepts expression which finds a non-literal substring in a string', () => { - expectTypeOf<['in', ['downcase', 'C'], ['concat', 'ab', 'cd']]>().toExtend(); + expectTypeOf< + ['in', ['downcase', 'C'], ['concat', 'ab', 'cd']] + >().toExtend(); }); test('type accepts expression which finds an element in an array', () => { expectTypeOf<['in', 2, ['literal', [1, 2, 3]]]>().toExtend(); }); test('type accepts expression which finds a non-literal element in an array', () => { - expectTypeOf<['in', ['*', 2, 5], ['literal', [1, 10, 100]]]>().toExtend(); + expectTypeOf< + ['in', ['*', 2, 5], ['literal', [1, 10, 100]]] + >().toExtend(); }); }); @@ -200,7 +246,9 @@ describe('"index-of" expression', () => { expectTypeOf<['index-of', 'b', 'abc']>().toExtend(); }); test('type accepts expression which finds a non-literal substring in a string', () => { - expectTypeOf<['index-of', ['downcase', 'C'], ['concat', 'ab', 'cd']]>().toExtend(); + expectTypeOf< + ['index-of', ['downcase', 'C'], ['concat', 'ab', 'cd']] + >().toExtend(); }); test('type accepts expression which starts looking for the substring at a start index', () => { expectTypeOf<['index-of', 'a', 'abc', 1]>().toExtend(); @@ -212,13 +260,19 @@ describe('"index-of" expression', () => { expectTypeOf<['index-of', 2, ['literal', [1, 2, 3]]]>().toExtend(); }); test('type accepts expression which finds a non-literal element in an array', () => { - expectTypeOf<['index-of', ['*', 2, 5], ['literal', [1, 10, 100]]]>().toExtend(); + expectTypeOf< + ['index-of', ['*', 2, 5], ['literal', [1, 10, 100]]] + >().toExtend(); }); test('type accepts expression which starts looking for the element at a start index', () => { - expectTypeOf<['index-of', 1, ['literal', [1, 2, 3]], 1]>().toExtend(); + expectTypeOf< + ['index-of', 1, ['literal', [1, 2, 3]], 1] + >().toExtend(); }); test('type accepts expression which starts looking for the element at a non-literal start index', () => { - expectTypeOf<['index-of', 2, ['literal', [1, 2, 3]], ['+', 0, -1, 2]]>().toExtend(); + expectTypeOf< + ['index-of', 2, ['literal', [1, 2, 3]], ['+', 0, -1, 2]] + >().toExtend(); }); }); @@ -273,15 +327,21 @@ describe('"slice" expression', () => { describe('comparison expressions', () => { describe('"!=" expression', () => { test('type accepts expression which compares against literal null value', () => { - expectTypeOf<['!=', null, ['get', 'nonexistent-prop']]>().toExtend(); + expectTypeOf< + ['!=', null, ['get', 'nonexistent-prop']] + >().toExtend(); }); }); describe('"==" expression', () => { test('type accepts expression which compares expression input against literal input', () => { - expectTypeOf<['==', ['get', 'MILITARYAIRPORT'], 1]>().toExtend(); + expectTypeOf< + ['==', ['get', 'MILITARYAIRPORT'], 1] + >().toExtend(); }); test('type accepts expression which compares against literal null value', () => { - expectTypeOf<['==', null, ['get', 'nonexistent-prop']]>().toExtend(); + expectTypeOf< + ['==', null, ['get', 'nonexistent-prop']] + >().toExtend(); }); }); describe('"<" expression', () => { @@ -314,25 +374,37 @@ describe('"any" expression', () => { describe('"case" expression', () => { test('type accepts expression which returns the string output of the first matching condition', () => { - expectTypeOf<[ - 'case', - ['==', ['get', 'CAPITAL'], 1], 'city-capital', - ['>=', ['get', 'POPULATION'], 1000000], 'city-1M', - ['>=', ['get', 'POPULATION'], 500000], 'city-500k', - ['>=', ['get', 'POPULATION'], 100000], 'city-100k', - 'city', - ]>().toExtend(); + expectTypeOf< + [ + 'case', + ['==', ['get', 'CAPITAL'], 1], + 'city-capital', + ['>=', ['get', 'POPULATION'], 1000000], + 'city-1M', + ['>=', ['get', 'POPULATION'], 500000], + 'city-500k', + ['>=', ['get', 'POPULATION'], 100000], + 'city-100k', + 'city' + ] + >().toExtend(); }); test('type accepts expression which returns the evaluated output of the first matching condition', () => { - expectTypeOf<[ - 'case', - ['has', 'point_count'], ['interpolate', ['linear'], ['get', 'point_count'], 2, '#ccc', 10, '#444'], - ['has', 'priorityValue'], ['interpolate', ['linear'], ['get', 'priorityValue'], 0, '#ff9', 1, '#f66'], - '#fcaf3e', - ]>().toExtend(); + expectTypeOf< + [ + 'case', + ['has', 'point_count'], + ['interpolate', ['linear'], ['get', 'point_count'], 2, '#ccc', 10, '#444'], + ['has', 'priorityValue'], + ['interpolate', ['linear'], ['get', 'priorityValue'], 0, '#ff9', 1, '#f66'], + '#fcaf3e' + ] + >().toExtend(); }); test('type accepts expression which has literal null output', () => { - expectTypeOf<['case', false, ['get', 'prop'], true, null, 'fallback']>().toExtend(); + expectTypeOf< + ['case', false, ['get', 'prop'], true, null, 'fallback'] + >().toExtend(); }); test('type accepts expression which has literal null fallback', () => { expectTypeOf<['case', false, ['get', 'prop'], null]>().toExtend(); @@ -341,31 +413,53 @@ describe('"case" expression', () => { describe('"match" expression', () => { test('type requires label to be string literal, number literal, string literal array, or number literal array', () => { - expectTypeOf<['match', 4, true, 'matched', 'fallback']>().not.toExtend(); - expectTypeOf<['match', 4, [true], 'matched', 'fallback']>().not.toExtend(); - expectTypeOf<['match', 4, [4, '4'], 'matched', 'fallback']>().not.toExtend(); - expectTypeOf<['match', 4, ['literal', [4]], 'matched', 'fallback']>().not.toExtend(); + expectTypeOf< + ['match', 4, true, 'matched', 'fallback'] + >().not.toExtend(); + expectTypeOf< + ['match', 4, [true], 'matched', 'fallback'] + >().not.toExtend(); + expectTypeOf< + ['match', 4, [4, '4'], 'matched', 'fallback'] + >().not.toExtend(); + expectTypeOf< + ['match', 4, ['literal', [4]], 'matched', 'fallback'] + >().not.toExtend(); }); test('type accepts expression which matches number input against number label', () => { - expectTypeOf<['match', 2, [0], 'o1', 1, 'o2', 2, 'o3', 'fallback']>().toExtend(); + expectTypeOf< + ['match', 2, [0], 'o1', 1, 'o2', 2, 'o3', 'fallback'] + >().toExtend(); }); test('type accepts expression which matches string input against string label', () => { - expectTypeOf<['match', 'c', 'a', 'o1', ['b'], 'o2', 'c', 'o3', 'fallback']>().toExtend(); + expectTypeOf< + ['match', 'c', 'a', 'o1', ['b'], 'o2', 'c', 'o3', 'fallback'] + >().toExtend(); }); test('type accepts expression which matches number input against number array label', () => { - expectTypeOf<['match', 2, 0, 'o1', [1, 2, 3], 'o2', 'fallback']>().toExtend(); + expectTypeOf< + ['match', 2, 0, 'o1', [1, 2, 3], 'o2', 'fallback'] + >().toExtend(); }); test('type accepts expression which matches string input against string array label', () => { - expectTypeOf<['match', 'c', 'a', 'o1', ['b', 'c', 'd'], 'o2', 'fallback']>().toExtend(); + expectTypeOf< + ['match', 'c', 'a', 'o1', ['b', 'c', 'd'], 'o2', 'fallback'] + >().toExtend(); }); test('type accepts expression which has a non-literal input', () => { - expectTypeOf<['match', ['get', 'TYPE'], ['ADIZ', 'AMA', 'AWY'], true, false]>().toExtend(); + expectTypeOf< + ['match', ['get', 'TYPE'], ['ADIZ', 'AMA', 'AWY'], true, false] + >().toExtend(); }); test('type accepts expression which has an expression output', () => { - expectTypeOf<['match', ['get', 'id'], 'exampleID', ['get', 'iconNameFocused'], ['get', 'iconName']]>().toExtend(); + expectTypeOf< + ['match', ['get', 'id'], 'exampleID', ['get', 'iconNameFocused'], ['get', 'iconName']] + >().toExtend(); }); test('type accepts expression which has literal null output', () => { - expectTypeOf<['match', 1, 0, ['get', 'prop'], 1, null, 'fallback']>().toExtend(); + expectTypeOf< + ['match', 1, 0, ['get', 'prop'], 1, null, 'fallback'] + >().toExtend(); }); }); @@ -374,124 +468,295 @@ describe('"within" expression', () => { expectTypeOf<['within']>().not.toExtend(); }); test('type rejects an expression as input', () => { - expectTypeOf<['within', ['literal', {type: 'Polygon'; coordinates: []}]]>().not.toExtend(); + expectTypeOf< + ['within', ['literal', {type: 'Polygon'; coordinates: []}]] + >().not.toExtend(); }); test('type rejects a second argument', () => { - expectTypeOf<['within', {type: 'Polygon'; coordinates: []}, 'second arg']>().not.toExtend(); + expectTypeOf< + ['within', {type: 'Polygon'; coordinates: []}, 'second arg'] + >().not.toExtend(); }); test('type accepts expression which checks if feature fully contained within input GeoJSON geometry', () => { - expectTypeOf<['within', { - type: 'Polygon'; - coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]; - }]>().toExtend(); + expectTypeOf< + [ + 'within', + { + type: 'Polygon'; + coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]; + } + ] + >().toExtend(); }); }); describe('interpolation expressions', () => { describe('linear interpolation type', () => { test('type works with "interpolate" expression', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 0, 10, 1, 20]>().toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['zoom'], 0, 10, 1, 20] + >().toExtend(); }); }); describe('exponential interpolation type', () => { test('type requires a number literal as the base argument', () => { - expectTypeOf<['interpolate', ['exponential', ['+', 0.1, 0.4]], ['zoom'], 0, 10, 1, 100]>().not.toExtend(); + expectTypeOf< + ['interpolate', ['exponential', ['+', 0.1, 0.4]], ['zoom'], 0, 10, 1, 100] + >().not.toExtend(); }); test('type works with "interpolate" expression', () => { - expectTypeOf<['interpolate', ['exponential', 1.1], ['zoom'], 0, 10, 1, 20]>().toExtend(); + expectTypeOf< + ['interpolate', ['exponential', 1.1], ['zoom'], 0, 10, 1, 20] + >().toExtend(); }); }); describe('cubic-bezier interpolation type', () => { test('type requires four numeric literal control point arguments', () => { - expectTypeOf<['interpolate', ['cubic-bezier', 0.4, 0, ['literal', 0.6], 1], ['zoom'], 2, 0, 8, 100]>().not.toExtend(); + expectTypeOf< + [ + 'interpolate', + ['cubic-bezier', 0.4, 0, ['literal', 0.6], 1], + ['zoom'], + 2, + 0, + 8, + 100 + ] + >().not.toExtend(); }); test('type rejects a fifth control point argument', () => { - expectTypeOf<['interpolate', ['cubic-bezier', 0.4, 0, 0.6, 1, 0.8], ['zoom'], 2, 0, 8, 100]>().not.toExtend(); + expectTypeOf< + ['interpolate', ['cubic-bezier', 0.4, 0, 0.6, 1, 0.8], ['zoom'], 2, 0, 8, 100] + >().not.toExtend(); }); test('type works with "interpolate" expression', () => { - expectTypeOf<['interpolate', ['cubic-bezier', 0.4, 0, 0.6, 1], ['zoom'], 0, 0, 10, 100]>().toExtend(); + expectTypeOf< + ['interpolate', ['cubic-bezier', 0.4, 0, 0.6, 1], ['zoom'], 0, 0, 10, 100] + >().toExtend(); }); }); describe('"interpolate" expression', () => { test('type requires stop outputs to be a number, color, number array, color array, or projection', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 0, false, 2, 1024]>().not.toExtend(); - expectTypeOf<['interpolate', ['linear'], ['zoom'], 0, [10, 20, 30], 0.5, [20, 30, 40], 1, [30, 40, 50]]>().not.toExtend(); - expectTypeOf<['interpolate', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}]>().not.toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['zoom'], 0, false, 2, 1024] + >().not.toExtend(); + expectTypeOf< + [ + 'interpolate', + ['linear'], + ['zoom'], + 0, + [10, 20, 30], + 0.5, + [20, 30, 40], + 1, + [30, 40, 50] + ] + >().not.toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}] + >().not.toExtend(); }); test('type accepts expression which interpolates with feature property input', () => { - expectTypeOf<['interpolate', ['linear'], ['get', 'point_count'], 2, ['/', 2, ['get', 'point_count']], 10, ['*', 4, ['get', 'point_count']]]>().toExtend(); + expectTypeOf< + [ + 'interpolate', + ['linear'], + ['get', 'point_count'], + 2, + ['/', 2, ['get', 'point_count']], + 10, + ['*', 4, ['get', 'point_count']] + ] + >().toExtend(); }); test('type accepts expression which interpolates between number outputs', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 0, 0, 0.5, ['*', 2, 5], 1, 100]>().toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['zoom'], 0, 0, 0.5, ['*', 2, 5], 1, 100] + >().toExtend(); }); test('type accepts expression which interpolates between color outputs', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 2, 'white', 4, 'black']>().toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['zoom'], 2, 'white', 4, 'black'] + >().toExtend(); }); test('type accepts expression which interpolates between number array outputs', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 8, ['literal', [2, 3]], 10, ['literal', [4, 5]]]>().toExtend(); + expectTypeOf< + [ + 'interpolate', + ['linear'], + ['zoom'], + 8, + ['literal', [2, 3]], + 10, + ['literal', [4, 5]] + ] + >().toExtend(); }); test('type accepts expression which interpolates between color array outputs', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 8, ['literal', ['white', 'black']], 10, ['literal', ['black', 'white']]]>().toExtend(); + expectTypeOf< + [ + 'interpolate', + ['linear'], + ['zoom'], + 8, + ['literal', ['white', 'black']], + 10, + ['literal', ['black', 'white']] + ] + >().toExtend(); }); test('type accepts expression which interpolates between projection outputs', () => { - expectTypeOf<['interpolate', ['linear'], ['zoom'], 8, 'vertical-perspective', 10, 'mercator']>().toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['zoom'], 8, 'vertical-perspective', 10, 'mercator'] + >().toExtend(); }); }); describe('"interpolate-hcl" expression', () => { test('type requires stop outputs to be a color', () => { - expectTypeOf<['interpolate-hcl', ['linear'], ['zoom'], 0, false, 2, 1024]>().not.toExtend(); - expectTypeOf<['interpolate-hcl', ['linear'], ['zoom'], 0, [10, 20, 30], 0.5, [20, 30, 40], 1, [30, 40, 50]]>().not.toExtend(); - expectTypeOf<['interpolate-hcl', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}]>().not.toExtend(); + expectTypeOf< + ['interpolate-hcl', ['linear'], ['zoom'], 0, false, 2, 1024] + >().not.toExtend(); + expectTypeOf< + [ + 'interpolate-hcl', + ['linear'], + ['zoom'], + 0, + [10, 20, 30], + 0.5, + [20, 30, 40], + 1, + [30, 40, 50] + ] + >().not.toExtend(); + expectTypeOf< + ['interpolate-hcl', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}] + >().not.toExtend(); }); test('type accepts expression which interpolates between color outputs', () => { - expectTypeOf<['interpolate-hcl', ['linear'], ['zoom'], 2, 'white', 4, 'black']>().toExtend(); + expectTypeOf< + ['interpolate-hcl', ['linear'], ['zoom'], 2, 'white', 4, 'black'] + >().toExtend(); }); test('type accepts expression which interpolates between color array outputs', () => { - expectTypeOf<['interpolate-hcl', ['linear'], ['zoom'], 8, ['literal', ['white', 'black']], 10, ['literal', ['black', 'white']]]>().toExtend(); + expectTypeOf< + [ + 'interpolate-hcl', + ['linear'], + ['zoom'], + 8, + ['literal', ['white', 'black']], + 10, + ['literal', ['black', 'white']] + ] + >().toExtend(); }); test('type accepts expression which interpolates between non-literal color array outputs', () => { // eslint-disable-next-line const obj = {'colors-8': ['white', 'black'], 'colors-10': ['black', 'white']}; - expectTypeOf<['interpolate-hcl', ['linear'], ['zoom'], 8, ['get', 'colors-8', ['literal', typeof obj]], 10, ['get', 'colors-10', ['literal', typeof obj]]]>().toExtend(); + expectTypeOf< + [ + 'interpolate-hcl', + ['linear'], + ['zoom'], + 8, + ['get', 'colors-8', ['literal', typeof obj]], + 10, + ['get', 'colors-10', ['literal', typeof obj]] + ] + >().toExtend(); }); }); describe('"interpolate-lab" expression', () => { test('type requires stop outputs to be a color', () => { - expectTypeOf<['interpolate-lab', ['linear'], ['zoom'], 0, false, 2, 1024]>().not.toExtend(); - expectTypeOf<['interpolate-lab', ['linear'], ['zoom'], 0, [10, 20, 30], 0.5, [20, 30, 40], 1, [30, 40, 50]]>().not.toExtend(); - expectTypeOf<['interpolate-lab', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}]>().not.toExtend(); + expectTypeOf< + ['interpolate-lab', ['linear'], ['zoom'], 0, false, 2, 1024] + >().not.toExtend(); + expectTypeOf< + [ + 'interpolate-lab', + ['linear'], + ['zoom'], + 0, + [10, 20, 30], + 0.5, + [20, 30, 40], + 1, + [30, 40, 50] + ] + >().not.toExtend(); + expectTypeOf< + ['interpolate-lab', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}] + >().not.toExtend(); }); test('type accepts expression which interpolates between color outputs', () => { - expectTypeOf<['interpolate-lab', ['linear'], ['zoom'], 2, 'white', 4, 'black']>().toExtend(); + expectTypeOf< + ['interpolate-lab', ['linear'], ['zoom'], 2, 'white', 4, 'black'] + >().toExtend(); }); test('type accepts expression which interpolates between color array outputs', () => { - expectTypeOf<['interpolate-lab', ['linear'], ['zoom'], 8, ['literal', ['white', 'black']], 10, ['literal', ['black', 'white']]]>().toExtend(); + expectTypeOf< + [ + 'interpolate-lab', + ['linear'], + ['zoom'], + 8, + ['literal', ['white', 'black']], + 10, + ['literal', ['black', 'white']] + ] + >().toExtend(); }); test('type accepts expression which interpolates between non-literal color array outputs', () => { // eslint-disable-next-line const obj = {'colors-8': ['white', 'black'], 'colors-10': ['black', 'white']}; - expectTypeOf<['interpolate-lab', ['linear'], ['zoom'], 8, ['get', 'colors-8', ['literal', typeof obj]], 10, ['get', 'colors-10', ['literal', typeof obj]]]>().toExtend(); + expectTypeOf< + [ + 'interpolate-lab', + ['linear'], + ['zoom'], + 8, + ['get', 'colors-8', ['literal', typeof obj]], + 10, + ['get', 'colors-10', ['literal', typeof obj]] + ] + >().toExtend(); }); }); }); describe('"step" expression', () => { test('type accepts expression which outputs stepped numbers', () => { - expectTypeOf<['step', ['get', 'point_count'], 0.6, 50, 0.7, 200, 0.8]>().toExtend(); + expectTypeOf< + ['step', ['get', 'point_count'], 0.6, 50, 0.7, 200, 0.8] + >().toExtend(); }); test('type accepts expression which outputs stepped colors', () => { - expectTypeOf<['step', ['get', 'point_count'], '#ddd', 50, '#eee', 200, '#fff']>().toExtend(); + expectTypeOf< + ['step', ['get', 'point_count'], '#ddd', 50, '#eee', 200, '#fff'] + >().toExtend(); }); test('type accepts expression which outputs stepped projections', () => { - expectTypeOf<['step', ['zoom'], 'vertical-perspective', 10, 'mercator']>().toExtend(); + expectTypeOf< + ['step', ['zoom'], 'vertical-perspective', 10, 'mercator'] + >().toExtend(); }); test('type accepts expression which outputs stepped multi-input projections', () => { - expectTypeOf<['step', ['zoom'], ['literal', ['vertical-perspective', 'mercator', 0.5]], 10, 'mercator']>().toExtend(); + expectTypeOf< + [ + 'step', + ['zoom'], + ['literal', ['vertical-perspective', 'mercator', 0.5]], + 10, + 'mercator' + ] + >().toExtend(); }); }); @@ -506,7 +771,10 @@ describe('"e" expression', () => { describe('nonexistent operators', () => { test('ExpressionSpecification type does not contain "ExpressionSpecification" expression', () => { - type ExpressionSpecificationExpression = Extract; + type ExpressionSpecificationExpression = Extract< + ExpressionSpecification, + ['ExpressionSpecification', ...any[]] + >; expectTypeOf().not.toExtend(); }); }); @@ -516,31 +784,28 @@ test('ExpressionSpecification type supports common variable insertion patterns', // As in most cases the styling is read from JSON, these are rather optional tests. // eslint-disable-next-line const colorStops = [0, 'red', 0.5, 'green', 1, 'blue']; - expectTypeOf<[ - 'interpolate', - ['linear'], - ['line-progress'], - ...typeof colorStops - ]>().toExtend(); - expectTypeOf<[ - 'interpolate-hcl', - ['linear'], - ['line-progress'], - ...typeof colorStops - ]>().toExtend(); - expectTypeOf<[ - 'interpolate-lab', - ['linear'], - ['line-progress'], - ...typeof colorStops - ]>().toExtend(); + expectTypeOf< + ['interpolate', ['linear'], ['line-progress'], ...typeof colorStops] + >().toExtend(); + expectTypeOf< + ['interpolate-hcl', ['linear'], ['line-progress'], ...typeof colorStops] + >().toExtend(); + expectTypeOf< + ['interpolate-lab', ['linear'], ['line-progress'], ...typeof colorStops] + >().toExtend(); // eslint-disable-next-line const [firstOutput, ...steps] = ['#df2d43', 50, '#df2d43', 200, '#df2d43']; - expectTypeOf<['step', ['get', 'point_count'], typeof firstOutput, ...typeof steps]>().toExtend(); + expectTypeOf< + ['step', ['get', 'point_count'], typeof firstOutput, ...typeof steps] + >().toExtend(); // eslint-disable-next-line const strings = ['first', 'second', 'third']; expectTypeOf<['concat', ...typeof strings]>().toExtend(); // eslint-disable-next-line - const values: (ExpressionInputType | ExpressionSpecification)[] = [['get', 'name'], ['get', 'code'], 'NONE']; // type is necessary! + const values: (ExpressionInputType | ExpressionSpecification)[] = [ + ['get', 'name'], + ['get', 'code'], + 'NONE' + ]; // type is necessary! expectTypeOf<['coalesce', ...typeof values]>().toExtend(); }); diff --git a/src/expression/expression.test.ts b/src/expression/expression.test.ts index b9ab7e84e..026957a5f 100644 --- a/src/expression/expression.test.ts +++ b/src/expression/expression.test.ts @@ -1,4 +1,9 @@ -import {createPropertyExpression, Feature, GlobalProperties, StylePropertyExpression} from '../expression'; +import { + createPropertyExpression, + Feature, + GlobalProperties, + StylePropertyExpression +} from '../expression'; import {expressions} from './definitions'; import v8 from '../reference/v8.json' with {type: 'json'}; import {createExpression, StylePropertySpecification} from '..'; @@ -8,9 +13,11 @@ import {describe, test, expect, vi} from 'vitest'; // filter out internal "error" and "filter-*" expressions from definition list const filterExpressionRegex = /filter-/; -const definitionList = Object.keys(expressions).filter((expression) => { - return expression !== 'error' && !filterExpressionRegex.exec(expression); -}).sort(); +const definitionList = Object.keys(expressions) + .filter((expression) => { + return expression !== 'error' && !filterExpressionRegex.exec(expression); + }) + .sort(); test('v8.json includes all definitions from style-spec', () => { const v8List = Object.keys(v8.expression_name.values); @@ -23,30 +30,41 @@ test('v8.json includes all definitions from style-spec', () => { describe('createPropertyExpression', () => { test('prohibits non-interpolable properties from using an "interpolate" expression', () => { - const {result, value} = createPropertyExpression([ - 'interpolate', ['linear'], ['zoom'], 0, 0, 10, 10 - ], { - type: 'number', - 'property-type': 'data-constant', - expression: { - 'interpolated': false, - 'parameters': ['zoom'] - } - } as StylePropertySpecification); + const {result, value} = createPropertyExpression( + ['interpolate', ['linear'], ['zoom'], 0, 0, 10, 10], + { + type: 'number', + 'property-type': 'data-constant', + expression: { + interpolated: false, + parameters: ['zoom'] + } + } as StylePropertySpecification + ); expect(result).toBe('error'); - expect((value as ExpressionParsingError[])).toHaveLength(1); - expect(value[0].message).toBe('"interpolate" expressions cannot be used with this property'); + expect(value as ExpressionParsingError[]).toHaveLength(1); + expect(value[0].message).toBe( + '"interpolate" expressions cannot be used with this property' + ); }); test('sets globalStateRefs', () => { - const {value} = createPropertyExpression(['case', ['>', ['global-state', 'stateKey'], 0], 100, ['global-state', 'anotherStateKey']], { - type: 'number', - 'property-type': 'data-driven', - expression: { - 'interpolated': false, - 'parameters': ['zoom', 'feature'] - } - } as any as StylePropertySpecification) as {value: StylePropertyExpression}; + const {value} = createPropertyExpression( + [ + 'case', + ['>', ['global-state', 'stateKey'], 0], + 100, + ['global-state', 'anotherStateKey'] + ], + { + type: 'number', + 'property-type': 'data-driven', + expression: { + interpolated: false, + parameters: ['zoom', 'feature'] + } + } as any as StylePropertySpecification + ) as {value: StylePropertyExpression}; expect(value.globalStateRefs).toEqual(new Set(['stateKey', 'anotherStateKey'])); }); @@ -58,7 +76,7 @@ describe('evaluate expression', () => { type: null, default: 42, 'property-type': 'data-driven', - transition: false, + transition: false }) as {value: StylePropertyExpression}; vi.spyOn(console, 'warn').mockImplementation(() => {}); @@ -71,12 +89,16 @@ describe('evaluate expression', () => { }); test('global state as expression property', () => { - const {value} = createPropertyExpression(['global-state', 'x'], { - type: null, - default: 42, - 'property-type': 'data-driven', - transition: false - }, {x: 5}) as {value: StylePropertyExpression}; + const {value} = createPropertyExpression( + ['global-state', 'x'], + { + type: null, + default: 42, + 'property-type': 'data-driven', + transition: false + }, + {x: 5} + ) as {value: StylePropertyExpression}; vi.spyOn(console, 'warn').mockImplementation(() => {}); @@ -85,15 +107,19 @@ describe('evaluate expression', () => { }); test('global state as expression property of zoom dependent expression', () => { - const {value} = createPropertyExpression(['interpolate', ['linear'], ['zoom'], 10, ['global-state', 'x'], 20, 50], { - type: 'number', - default: 42, - 'property-type': 'data-driven', - expression: { - interpolated: true, - parameters: ['zoom'] - } - } as StylePropertySpecification, {x: 5}) as {value: StylePropertyExpression}; + const {value} = createPropertyExpression( + ['interpolate', ['linear'], ['zoom'], 10, ['global-state', 'x'], 20, 50], + { + type: 'number', + default: 42, + 'property-type': 'data-driven', + expression: { + interpolated: true, + parameters: ['zoom'] + } + } as StylePropertySpecification, + {x: 5} + ) as {value: StylePropertyExpression}; vi.spyOn(console, 'warn').mockImplementation(() => {}); @@ -108,8 +134,8 @@ describe('evaluate expression', () => { default: 'a', 'property-type': 'data-driven', expression: { - 'interpolated': false, - 'parameters': ['zoom', 'feature'] + interpolated: false, + parameters: ['zoom', 'feature'] } } as any as StylePropertySpecification) as {value: StylePropertyExpression}; @@ -117,9 +143,15 @@ describe('evaluate expression', () => { expect(value.kind).toBe('source'); - expect(value.evaluate({} as GlobalProperties, {properties: {x: 'b'}} as any as Feature)).toBe('b'); - expect(value.evaluate({} as GlobalProperties, {properties: {x: 'invalid'}} as any as Feature)).toBe('a'); - expect(console.warn).toHaveBeenCalledWith('Expected value to be one of "a", "b", "c", but found "invalid" instead.'); + expect( + value.evaluate({} as GlobalProperties, {properties: {x: 'b'}} as any as Feature) + ).toBe('b'); + expect( + value.evaluate({} as GlobalProperties, {properties: {x: 'invalid'}} as any as Feature) + ).toBe('a'); + expect(console.warn).toHaveBeenCalledWith( + 'Expected value to be one of "a", "b", "c", but found "invalid" instead.' + ); }); test('warns for invalid variableAnchorOffsetCollection values', () => { @@ -128,8 +160,8 @@ describe('evaluate expression', () => { 'property-type': 'data-driven', transition: false, expression: { - 'interpolated': false, - 'parameters': ['zoom', 'feature'] + interpolated: false, + parameters: ['zoom', 'feature'] } }) as {value: StylePropertyExpression}; @@ -137,12 +169,21 @@ describe('evaluate expression', () => { expect(value.kind).toBe('source'); - expect(value.evaluate({} as GlobalProperties, {properties: {x: 'invalid'}} as any as Feature)).toBeNull(); + expect( + value.evaluate({} as GlobalProperties, {properties: {x: 'invalid'}} as any as Feature) + ).toBeNull(); expect(console.warn).toHaveBeenCalledTimes(2); - expect(console.warn).toHaveBeenCalledWith('Could not parse variableAnchorOffsetCollection from value \'invalid\''); + expect(console.warn).toHaveBeenCalledWith( + "Could not parse variableAnchorOffsetCollection from value 'invalid'" + ); warnMock.mockClear(); - expect(value.evaluate({} as GlobalProperties, {properties: {x: ['top', [2, 2]]}} as any as Feature)).toEqual(new VariableAnchorOffsetCollection(['top', [2, 2]])); + expect( + value.evaluate( + {} as GlobalProperties, + {properties: {x: ['top', [2, 2]]}} as any as Feature + ) + ).toEqual(new VariableAnchorOffsetCollection(['top', [2, 2]])); expect(console.warn).not.toHaveBeenCalled(); }); }); @@ -151,6 +192,8 @@ describe('nonexistent operators', () => { test('"ExpressionSpecification" operator does not exist', () => { const response = createExpression(['ExpressionSpecification']); expect(response.result).toBe('error'); - expect((response.value as ExpressionParsingError[])[0].message).toContain('Unknown expression \"ExpressionSpecification\".'); + expect((response.value as ExpressionParsingError[])[0].message).toContain( + 'Unknown expression \"ExpressionSpecification\".' + ); }); }); diff --git a/src/expression/expression.ts b/src/expression/expression.ts index 0e62c4a49..3ebef470c 100644 --- a/src/expression/expression.ts +++ b/src/expression/expression.ts @@ -16,7 +16,10 @@ export interface Expression { outputDefined(): boolean; } -export type ExpressionParser = (args: ReadonlyArray, context: ParsingContext) => Expression; +export type ExpressionParser = ( + args: ReadonlyArray, + context: ParsingContext +) => Expression; export type ExpressionRegistration = { new (...args: any): Expression; } & { diff --git a/src/expression/index.test.ts b/src/expression/index.test.ts index 3ab01a7e9..6fdd20c7f 100644 --- a/src/expression/index.test.ts +++ b/src/expression/index.test.ts @@ -1,5 +1,4 @@ - -import {normalizePropertyExpression, StyleExpression} from '.' +import {normalizePropertyExpression, StyleExpression} from '.'; import {StylePropertySpecification} from '..'; import {Color} from './types/color'; import {ColorArray} from './types/color_array'; @@ -16,124 +15,151 @@ function stylePropertySpecification(type): StylePropertySpecification { interpolated: false, parameters: [] }, - transition: false + transition: false }; -}; +} describe('normalizePropertyExpression expressions', () => { - test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(['literal', ['#FF0000', 'black']], - stylePropertySpecification('colorArray')); + const expression = normalizePropertyExpression( + ['literal', ['#FF0000', 'black']], + stylePropertySpecification('colorArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([Color.red, Color.black]); - }) + }); test('normalizePropertyExpression single value', () => { - const expression = normalizePropertyExpression(['literal', '#FF0000'], - stylePropertySpecification('colorArray')); + const expression = normalizePropertyExpression( + ['literal', '#FF0000'], + stylePropertySpecification('colorArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([Color.red]); - }) + }); test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(['literal', [1, 2]], - stylePropertySpecification('numberArray')); + const expression = normalizePropertyExpression( + ['literal', [1, 2]], + stylePropertySpecification('numberArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([1, 2]); - }) + }); test('normalizePropertyExpression single value', () => { - const expression = normalizePropertyExpression(['literal', 1], - stylePropertySpecification('numberArray')); + const expression = normalizePropertyExpression( + ['literal', 1], + stylePropertySpecification('numberArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([1]); - }) + }); test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(['literal', [1,2]], - stylePropertySpecification('padding')); - expect(expression.evaluate({zoom: 0}).values).toEqual([1,2,1,2]); - }) - + const expression = normalizePropertyExpression( + ['literal', [1, 2]], + stylePropertySpecification('padding') + ); + expect(expression.evaluate({zoom: 0}).values).toEqual([1, 2, 1, 2]); + }); }); describe('normalizePropertyExpression objects', () => { - test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(ColorArray.parse(['#FF0000', 'black']), - stylePropertySpecification('colorArray')); + const expression = normalizePropertyExpression( + ColorArray.parse(['#FF0000', 'black']), + stylePropertySpecification('colorArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([Color.red, Color.black]); - }) + }); test('normalizePropertyExpression single value', () => { - const expression = normalizePropertyExpression(ColorArray.parse('#FF0000'), - stylePropertySpecification('colorArray')); + const expression = normalizePropertyExpression( + ColorArray.parse('#FF0000'), + stylePropertySpecification('colorArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([Color.red]); - }) + }); test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(NumberArray.parse([1, 2]), - stylePropertySpecification('numberArray')); + const expression = normalizePropertyExpression( + NumberArray.parse([1, 2]), + stylePropertySpecification('numberArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([1, 2]); - }) + }); test('normalizePropertyExpression single value', () => { - const expression = normalizePropertyExpression(NumberArray.parse(1), - stylePropertySpecification('numberArray')); + const expression = normalizePropertyExpression( + NumberArray.parse(1), + stylePropertySpecification('numberArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([1]); - }) + }); test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(Padding.parse([1,2]), - stylePropertySpecification('padding')); - expect(expression.evaluate({zoom: 0}).values).toEqual([1,2,1,2]); - }) - + const expression = normalizePropertyExpression( + Padding.parse([1, 2]), + stylePropertySpecification('padding') + ); + expect(expression.evaluate({zoom: 0}).values).toEqual([1, 2, 1, 2]); + }); }); describe('normalizePropertyExpression raw values', () => { - test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression(['#FF0000', 'black'] as any, - stylePropertySpecification('colorArray')); + const expression = normalizePropertyExpression( + ['#FF0000', 'black'] as any, + stylePropertySpecification('colorArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([Color.red, Color.black]); - }) + }); test('normalizePropertyExpression single value', () => { - const expression = normalizePropertyExpression('#FF0000' as any, - stylePropertySpecification('colorArray')); + const expression = normalizePropertyExpression( + '#FF0000' as any, + stylePropertySpecification('colorArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([Color.red]); - }) + }); test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression([1, 2] as any, - stylePropertySpecification('numberArray')); + const expression = normalizePropertyExpression( + [1, 2] as any, + stylePropertySpecification('numberArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([1, 2]); - }) + }); test('normalizePropertyExpression single value', () => { - const expression = normalizePropertyExpression(1 as any, - stylePropertySpecification('numberArray')); + const expression = normalizePropertyExpression( + 1 as any, + stylePropertySpecification('numberArray') + ); expect(expression.evaluate({zoom: 0}).values).toEqual([1]); - }) + }); test('normalizePropertyExpression', () => { - const expression = normalizePropertyExpression([1,2] as any, - stylePropertySpecification('padding')); - expect(expression.evaluate({zoom: 0}).values).toEqual([1,2,1,2]); - }) - + const expression = normalizePropertyExpression( + [1, 2] as any, + stylePropertySpecification('padding') + ); + expect(expression.evaluate({zoom: 0}).values).toEqual([1, 2, 1, 2]); + }); }); describe('StyleExpressions', () => { - test('ignore random fields when adding global state ', () => { const expression = { evaluate: vi.fn() } as any as Expression; - const styleExpression = new StyleExpression(expression, { - type: null, - default: 42, - 'property-type': 'data-driven', - transition: false - } as StylePropertySpecification, {x: 5} as Record); + const styleExpression = new StyleExpression( + expression, + { + type: null, + default: 42, + 'property-type': 'data-driven', + transition: false + } as StylePropertySpecification, + {x: 5} as Record + ); styleExpression.evaluate({zoom: 10, a: 20, b: 30} as any); expect(expression.evaluate).toHaveBeenCalled(); @@ -143,5 +169,4 @@ describe('StyleExpressions', () => { expect(params).not.toHaveProperty('a'); expect(params).not.toHaveProperty('b'); }); - -}); \ No newline at end of file +}); diff --git a/src/expression/index.ts b/src/expression/index.ts index edd3f3626..9b0b13eed 100644 --- a/src/expression/index.ts +++ b/src/expression/index.ts @@ -1,10 +1,11 @@ - import {extendBy} from '../util/extend'; import {ExpressionParsingError} from './parsing_error'; import {ParsingContext} from './parsing_context'; import {EvaluationContext} from './evaluation_context'; -import {CompoundExpression, isFeatureConstant, +import { + CompoundExpression, + isFeatureConstant, isGlobalPropertyConstant, isStateConstant, isExpressionConstant @@ -18,15 +19,41 @@ import {expressions} from './definitions'; import {RuntimeError} from './runtime_error'; import {success, error} from '../util/result'; -import {supportsPropertyExpression, supportsZoomExpression, supportsInterpolation} from '../util/properties'; - -import {ColorType, StringType, NumberType, BooleanType, ValueType, FormattedType, PaddingType, ResolvedImageType, VariableAnchorOffsetCollectionType, array, type Type, type EvaluationKind, ProjectionDefinitionType, NumberArrayType, ColorArrayType} from './types'; +import { + supportsPropertyExpression, + supportsZoomExpression, + supportsInterpolation +} from '../util/properties'; + +import { + ColorType, + StringType, + NumberType, + BooleanType, + ValueType, + FormattedType, + PaddingType, + ResolvedImageType, + VariableAnchorOffsetCollectionType, + array, + type Type, + type EvaluationKind, + ProjectionDefinitionType, + NumberArrayType, + ColorArrayType +} from './types'; import type {Value} from './values'; import type {Expression} from './expression'; import {type StylePropertySpecification} from '..'; import type {Result} from '../util/result'; import type {InterpolationType} from './definitions/interpolate'; -import type {PaddingSpecification, NumberArraySpecification, ColorArraySpecification, PropertyValueSpecification, VariableAnchorOffsetCollectionSpecification} from '../types.g'; +import type { + PaddingSpecification, + NumberArraySpecification, + ColorArraySpecification, + PropertyValueSpecification, + VariableAnchorOffsetCollectionSpecification +} from '../types.g'; import type {FormattedSection} from './types/formatted'; import type {Point2D} from '../point2d'; @@ -41,21 +68,32 @@ import {ProjectionDefinition} from './types/projection_definition'; import {GlobalState} from './definitions/global_state'; export type Feature = { - readonly type: 0 | 1 | 2 | 3 | 'Unknown' | 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon'; + readonly type: + | 0 + | 1 + | 2 + | 3 + | 'Unknown' + | 'Point' + | 'MultiPoint' + | 'LineString' + | 'MultiLineString' + | 'Polygon' + | 'MultiPolygon'; readonly id?: any; readonly properties: {[_: string]: any}; readonly patterns?: { [_: string]: { - 'min': string; - 'mid': string; - 'max': string; + min: string; + mid: string; + max: string; }; }; readonly dashes?: { [_: string]: { - 'min': string; - 'mid': string; - 'max': string; + min: string; + mid: string; + max: string; }; }; readonly geometry?: Array>; @@ -82,12 +120,17 @@ export class StyleExpression { _enumValues: {[_: string]: any}; readonly _globalState: Record; - constructor(expression: Expression, propertySpec?: StylePropertySpecification | null, globalState?: Record) { + constructor( + expression: Expression, + propertySpec?: StylePropertySpecification | null, + globalState?: Record + ) { this.expression = expression; this._warningHistory = {}; this._evaluator = new EvaluationContext(); this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null; - this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null; + this._enumValues = + propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null; this._globalState = globalState; } @@ -136,7 +179,11 @@ export class StyleExpression { return this._defaultValue; } if (this._enumValues && !(val in this._enumValues)) { - throw new RuntimeError(`Expected value to be one of ${Object.keys(this._enumValues).map(v => JSON.stringify(v)).join(', ')}, but found ${JSON.stringify(val)} instead.`); + throw new RuntimeError( + `Expected value to be one of ${Object.keys(this._enumValues) + .map((v) => JSON.stringify(v)) + .join(', ')}, but found ${JSON.stringify(val)} instead.` + ); } return val; } catch (e) { @@ -152,8 +199,12 @@ export class StyleExpression { } export function isExpression(expression: unknown) { - return Array.isArray(expression) && expression.length > 0 && - typeof expression[0] === 'string' && expression[0] in expressions; + return ( + Array.isArray(expression) && + expression.length > 0 && + typeof expression[0] === 'string' && + expression[0] in expressions + ); } /** @@ -165,12 +216,26 @@ export function isExpression(expression: unknown) { * * @private */ -export function createExpression(expression: unknown, propertySpec?: StylePropertySpecification | null, globalState?: Record): Result> { - const parser = new ParsingContext(expressions, isExpressionConstant, [], propertySpec ? getExpectedType(propertySpec) : undefined); +export function createExpression( + expression: unknown, + propertySpec?: StylePropertySpecification | null, + globalState?: Record +): Result> { + const parser = new ParsingContext( + expressions, + isExpressionConstant, + [], + propertySpec ? getExpectedType(propertySpec) : undefined + ); // For string-valued properties, coerce to string at the top level rather than asserting. - const parsed = parser.parse(expression, undefined, undefined, undefined, - propertySpec && propertySpec.type === 'string' ? {typeAnnotation: 'coerce'} : undefined); + const parsed = parser.parse( + expression, + undefined, + undefined, + undefined, + propertySpec && propertySpec.type === 'string' ? {typeAnnotation: 'coerce'} : undefined + ); if (!parsed) { return error(parser.errors); @@ -189,7 +254,8 @@ export class ZoomConstantExpression { constructor(kind: Kind, expression: StyleExpression, globalState?: Record) { this.kind = kind; this._styleExpression = expression; - this.isStateDependent = kind !== ('constant' as EvaluationKind) && !isStateConstant(expression.expression); + this.isStateDependent = + kind !== ('constant' as EvaluationKind) && !isStateConstant(expression.expression); this.globalStateRefs = findGlobalStateRefs(expression.expression); this._globalState = globalState; } @@ -205,7 +271,14 @@ export class ZoomConstantExpression { if (this._globalState) { globals = addGlobalState(globals, this._globalState); } - return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection); + return this._styleExpression.evaluateWithoutErrorHandling( + globals, + feature, + featureState, + canonical, + availableImages, + formattedSection + ); } evaluate( @@ -219,7 +292,14 @@ export class ZoomConstantExpression { if (this._globalState) { globals = addGlobalState(globals, this._globalState); } - return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection); + return this._styleExpression.evaluate( + globals, + feature, + featureState, + canonical, + availableImages, + formattedSection + ); } } @@ -232,11 +312,18 @@ export class ZoomDependentExpression { interpolationType: InterpolationType; readonly _globalState: Record; - constructor(kind: Kind, expression: StyleExpression, zoomStops: Array, interpolationType?: InterpolationType, globalState?: Record) { + constructor( + kind: Kind, + expression: StyleExpression, + zoomStops: Array, + interpolationType?: InterpolationType, + globalState?: Record + ) { this.kind = kind; this.zoomStops = zoomStops; this._styleExpression = expression; - this.isStateDependent = kind !== ('camera' as EvaluationKind) && !isStateConstant(expression.expression); + this.isStateDependent = + kind !== ('camera' as EvaluationKind) && !isStateConstant(expression.expression); this.globalStateRefs = findGlobalStateRefs(expression.expression); this.interpolationType = interpolationType; this._globalState = globalState; @@ -253,7 +340,14 @@ export class ZoomDependentExpression { if (this._globalState) { globals = addGlobalState(globals, this._globalState); } - return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection); + return this._styleExpression.evaluateWithoutErrorHandling( + globals, + feature, + featureState, + canonical, + availableImages, + formattedSection + ); } evaluate( @@ -267,7 +361,14 @@ export class ZoomDependentExpression { if (this._globalState) { globals = addGlobalState(globals, this._globalState); } - return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection); + return this._styleExpression.evaluate( + globals, + feature, + featureState, + canonical, + availableImages, + formattedSection + ); } interpolationFactor(input: number, lower: number, upper: number): number { @@ -279,7 +380,9 @@ export class ZoomDependentExpression { } } -export function isZoomExpression(expression: any): expression is ZoomConstantExpression<'source'> | ZoomDependentExpression<'source'> { +export function isZoomExpression( + expression: any +): expression is ZoomConstantExpression<'source'> | ZoomDependentExpression<'source'> { return (expression as ZoomConstantExpression<'source'>)._styleExpression !== undefined; } @@ -345,9 +448,17 @@ export type CompositeExpression = { interpolationType: InterpolationType; }; -export type StylePropertyExpression = ConstantExpression | SourceExpression | CameraExpression | CompositeExpression; +export type StylePropertyExpression = + | ConstantExpression + | SourceExpression + | CameraExpression + | CompositeExpression; -export function createPropertyExpression(expressionInput: unknown, propertySpec: StylePropertySpecification, globalState?: Record): Result> { +export function createPropertyExpression( + expressionInput: unknown, + propertySpec: StylePropertySpecification, + globalState?: Record +): Result> { const expression = createExpression(expressionInput, propertySpec, globalState); if (expression.result === 'error') { return expression; @@ -367,24 +478,59 @@ export function createPropertyExpression(expressionInput: unknown, propertySpec: const zoomCurve = findZoomCurve(parsed); if (!zoomCurve && !isZoomConstant) { - return error([new ExpressionParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.')]); + return error([ + new ExpressionParsingError( + '', + '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.' + ) + ]); } else if (zoomCurve instanceof ExpressionParsingError) { return error([zoomCurve]); } else if (zoomCurve instanceof Interpolate && !supportsInterpolation(propertySpec)) { - return error([new ExpressionParsingError('', '"interpolate" expressions cannot be used with this property')]); + return error([ + new ExpressionParsingError( + '', + '"interpolate" expressions cannot be used with this property' + ) + ]); } if (!zoomCurve) { - return success(isFeatureConstantResult ? - (new ZoomConstantExpression('constant', expression.value, globalState) as ConstantExpression) : - (new ZoomConstantExpression('source', expression.value, globalState) as SourceExpression)); + return success( + isFeatureConstantResult + ? (new ZoomConstantExpression( + 'constant', + expression.value, + globalState + ) as ConstantExpression) + : (new ZoomConstantExpression( + 'source', + expression.value, + globalState + ) as SourceExpression) + ); } - const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined; - - return success(isFeatureConstantResult ? - (new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType, globalState) as CameraExpression) : - (new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType, globalState) as CompositeExpression)); + const interpolationType = + zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined; + + return success( + isFeatureConstantResult + ? (new ZoomDependentExpression( + 'camera', + expression.value, + zoomCurve.labels, + interpolationType, + globalState + ) as CameraExpression) + : (new ZoomDependentExpression( + 'composite', + expression.value, + zoomCurve.labels, + interpolationType, + globalState + ) as CompositeExpression) + ); } // serialization wrapper for old-style stop functions normalized to the @@ -395,10 +541,13 @@ export class StylePropertyFunction { kind: EvaluationKind; evaluate: (globals: GlobalProperties, feature?: Feature) => any; - interpolationFactor: ((input: number, lower: number, upper: number) => number); + interpolationFactor: (input: number, lower: number, upper: number) => number; zoomStops: Array; - constructor(parameters: PropertyValueSpecification, specification: StylePropertySpecification) { + constructor( + parameters: PropertyValueSpecification, + specification: StylePropertySpecification + ) { this._parameters = parameters; this._specification = specification; extendBy(this, createFunction(this._parameters, this._specification)); @@ -408,7 +557,10 @@ export class StylePropertyFunction { _parameters: PropertyValueSpecification; _specification: StylePropertySpecification; }) { - return new StylePropertyFunction(serialized._parameters, serialized._specification) as StylePropertyFunction; + return new StylePropertyFunction( + serialized._parameters, + serialized._specification + ) as StylePropertyFunction; } static serialize(input: StylePropertyFunction) { @@ -426,27 +578,39 @@ export function normalizePropertyExpression( ): StylePropertyExpression { if (isFunction(value)) { return new StylePropertyFunction(value, specification) as any; - } else if (isExpression(value)) { const expression = createPropertyExpression(value, specification, globalState); if (expression.result === 'error') { // this should have been caught in validation - throw new Error(expression.value.map(err => `${err.key}: ${err.message}`).join(', ')); + throw new Error(expression.value.map((err) => `${err.key}: ${err.message}`).join(', ')); } return expression.value; - } else { let constant: any = value; if (specification.type === 'color' && typeof value === 'string') { constant = Color.parse(value); - } else if (specification.type === 'padding' && (typeof value === 'number' || Array.isArray(value))) { + } else if ( + specification.type === 'padding' && + (typeof value === 'number' || Array.isArray(value)) + ) { constant = Padding.parse(value as PaddingSpecification); - } else if (specification.type === 'numberArray' && (typeof value === 'number' || Array.isArray(value))) { + } else if ( + specification.type === 'numberArray' && + (typeof value === 'number' || Array.isArray(value)) + ) { constant = NumberArray.parse(value as NumberArraySpecification); - } else if (specification.type === 'colorArray' && (typeof value === 'string' || Array.isArray(value))) { + } else if ( + specification.type === 'colorArray' && + (typeof value === 'string' || Array.isArray(value)) + ) { constant = ColorArray.parse(value as ColorArraySpecification); - } else if (specification.type === 'variableAnchorOffsetCollection' && Array.isArray(value)) { - constant = VariableAnchorOffsetCollection.parse(value as VariableAnchorOffsetCollectionSpecification); + } else if ( + specification.type === 'variableAnchorOffsetCollection' && + Array.isArray(value) + ) { + constant = VariableAnchorOffsetCollection.parse( + value as VariableAnchorOffsetCollectionSpecification + ); } else if (specification.type === 'projectionDefinition' && typeof value === 'string') { constant = ProjectionDefinition.parse(value); } @@ -466,7 +630,6 @@ function findZoomCurve(expression: Expression): Step | Interpolate | ExpressionP let result = null; if (expression instanceof Let) { result = findZoomCurve(expression.result); - } else if (expression instanceof Coalesce) { for (const arg of expression.args) { result = findZoomCurve(arg); @@ -474,11 +637,11 @@ function findZoomCurve(expression: Expression): Step | Interpolate | ExpressionP break; } } - - } else if ((expression instanceof Step || expression instanceof Interpolate) && + } else if ( + (expression instanceof Step || expression instanceof Interpolate) && expression.input instanceof CompoundExpression && - expression.input.name === 'zoom') { - + expression.input.name === 'zoom' + ) { result = expression; } @@ -491,21 +654,30 @@ function findZoomCurve(expression: Expression): Step | Interpolate | ExpressionP if (childResult instanceof ExpressionParsingError) { result = childResult; } else if (!result && childResult) { - result = new ExpressionParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.'); + result = new ExpressionParsingError( + '', + '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.' + ); } else if (result && childResult && result !== childResult) { - result = new ExpressionParsingError('', 'Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.'); + result = new ExpressionParsingError( + '', + 'Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.' + ); } }); return result; } -export function findGlobalStateRefs(expression: Expression, results = new Set()): Set { +export function findGlobalStateRefs( + expression: Expression, + results = new Set() +): Set { if (expression instanceof GlobalState) { results.add(expression.key); } - expression.eachChild(childExpression => { + expression.eachChild((childExpression) => { findGlobalStateRefs(childExpression, results); }); return results; @@ -555,19 +727,16 @@ function getDefaultValue(spec: StylePropertySpecification): Value { case 'projectionDefinition': return ProjectionDefinition.parse(spec.default) || null; default: - return (spec.default === undefined ? null : spec.default); + return spec.default === undefined ? null : spec.default; } } -function addGlobalState(globals: GlobalProperties, globalState: Record): GlobalProperties { - const { - zoom, - heatmapDensity, - elevation, - lineProgress, - isSupportedScript, - accumulated - } = globals ?? {}; +function addGlobalState( + globals: GlobalProperties, + globalState: Record +): GlobalProperties { + const {zoom, heatmapDensity, elevation, lineProgress, isSupportedScript, accumulated} = + globals ?? {}; return { zoom, heatmapDensity, diff --git a/src/expression/parsing_context.ts b/src/expression/parsing_context.ts index 41ce92675..2dea58c86 100644 --- a/src/expression/parsing_context.ts +++ b/src/expression/parsing_context.ts @@ -29,11 +29,11 @@ export class ParsingContext { /** * Internal delegate to inConstant function to avoid circular dependency to CompoundExpression */ - private _isConstant: (expression: Expression)=> boolean; + private _isConstant: (expression: Expression) => boolean; constructor( registry: ExpressionRegistry, - isConstantFunc: (expression: Expression)=> boolean, + isConstantFunc: (expression: Expression) => boolean, path: Array = [], expectedType?: Type | null, scope: Scope = new Scope(), @@ -41,7 +41,7 @@ export class ParsingContext { ) { this.registry = registry; this.path = path; - this.key = path.map(part => `[${part}]`).join(''); + this.key = path.map((part) => `[${part}]`).join(''); this.scope = scope; this.errors = errors; this.expectedType = expectedType; @@ -76,7 +76,12 @@ export class ParsingContext { typeAnnotation?: 'assert' | 'coerce' | 'omit'; } ): Expression { - if (expr === null || typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') { + if ( + expr === null || + typeof expr === 'string' || + typeof expr === 'boolean' || + typeof expr === 'number' + ) { expr = ['literal', expr]; } @@ -92,12 +97,17 @@ export class ParsingContext { if (Array.isArray(expr)) { if (expr.length === 0) { - return this.error('Expected an array with at least one element. If you wanted a literal array, use ["literal", []].') as null; + return this.error( + 'Expected an array with at least one element. If you wanted a literal array, use ["literal", []].' + ) as null; } const op = expr[0]; if (typeof op !== 'string') { - this.error(`Expression name must be a string, but found ${typeof op} instead. If you wanted a literal array, use ["literal", [...]].`, 0); + this.error( + `Expression name must be a string, but found ${typeof op} instead. If you wanted a literal array, use ["literal", [...]].`, + 0 + ); return null; } @@ -118,13 +128,27 @@ export class ParsingContext { // * The "coalesce" operator, which needs to omit type annotations. // * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion. // - if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') { + if ( + (expected.kind === 'string' || + expected.kind === 'number' || + expected.kind === 'boolean' || + expected.kind === 'object' || + expected.kind === 'array') && + actual.kind === 'value' + ) { parsed = annotate(parsed, expected, options.typeAnnotation || 'assert'); - } else if (('projectionDefinition' === expected.kind && ['string', 'array'].includes(actual.kind)) || - ((['color', 'formatted','resolvedImage'].includes(expected.kind)) && ['value','string'].includes(actual.kind)) || - ((['padding','numberArray'].includes(expected.kind)) && ['value', 'number', 'array'].includes(actual.kind)) || - ('colorArray' === expected.kind && ['value', 'string', 'array'].includes(actual.kind)) || - ('variableAnchorOffsetCollection' === expected.kind && ['value', 'array'].includes(actual.kind))) { + } else if ( + ('projectionDefinition' === expected.kind && + ['string', 'array'].includes(actual.kind)) || + (['color', 'formatted', 'resolvedImage'].includes(expected.kind) && + ['value', 'string'].includes(actual.kind)) || + (['padding', 'numberArray'].includes(expected.kind) && + ['value', 'number', 'array'].includes(actual.kind)) || + ('colorArray' === expected.kind && + ['value', 'string', 'array'].includes(actual.kind)) || + ('variableAnchorOffsetCollection' === expected.kind && + ['value', 'array'].includes(actual.kind)) + ) { parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce'); } else if (this.checkSubtype(expected, actual)) { return null; @@ -135,7 +159,11 @@ export class ParsingContext { // it immediately and replace it with a literal value in the // parsed/compiled result. Expressions that expect an image should // not be resolved here so we can later get the available images. - if (!(parsed instanceof Literal) && (parsed.type.kind !== 'resolvedImage') && this._isConstant(parsed)) { + if ( + !(parsed instanceof Literal) && + parsed.type.kind !== 'resolvedImage' && + this._isConstant(parsed) + ) { const ec = new EvaluationContext(); try { parsed = new Literal(parsed.type, parsed.evaluate(ec)); @@ -148,9 +176,12 @@ export class ParsingContext { return parsed; } - return this.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0) as null; + return this.error( + `Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, + 0 + ) as null; } else if (typeof expr === 'undefined') { - return this.error('\'undefined\' value invalid. Use null instead.') as null; + return this.error("'undefined' value invalid. Use null instead.") as null; } else if (typeof expr === 'object') { return this.error('Bare objects invalid. Use ["literal", {...}] instead.') as null; } else { @@ -187,7 +218,7 @@ export class ParsingContext { * @private */ error(error: string, ...keys: Array) { - const key = `${this.key}${keys.map(k => `[${k}]`).join('')}`; + const key = `${this.key}${keys.map((k) => `[${k}]`).join('')}`; this.errors.push(new ExpressionParsingError(key, error)); } @@ -204,4 +235,3 @@ export class ParsingContext { return error; } } - diff --git a/src/expression/scope.ts b/src/expression/scope.ts index 843f9e8f5..d744c7a2b 100644 --- a/src/expression/scope.ts +++ b/src/expression/scope.ts @@ -20,8 +20,12 @@ export class Scope { } get(name: string): Expression { - if (this.bindings[name]) { return this.bindings[name]; } - if (this.parent) { return this.parent.get(name); } + if (this.bindings[name]) { + return this.bindings[name]; + } + if (this.parent) { + return this.parent.get(name); + } throw new Error(`${name} not found in scope.`); } diff --git a/src/expression/stops.test.ts b/src/expression/stops.test.ts index ece11bfad..2bcf7307d 100644 --- a/src/expression/stops.test.ts +++ b/src/expression/stops.test.ts @@ -18,7 +18,5 @@ describe('findStopLessThanOrEqualTo', () => { index = findStopLessThanOrEqualTo([0.4, 0.5, 0.5, 0.6, 0.7], 0.5); expect(index).toBe(2); - }); - }); diff --git a/src/expression/stops.ts b/src/expression/stops.ts index 27f2f9903..10299ccc0 100644 --- a/src/expression/stops.ts +++ b/src/expression/stops.ts @@ -21,7 +21,8 @@ export function findStopLessThanOrEqualTo(stops: Array, input: number) { nextValue = stops[currentIndex + 1]; if (currentValue <= input) { - if (currentIndex === lastIndex || input < nextValue) { // Search complete + if (currentIndex === lastIndex || input < nextValue) { + // Search complete return currentIndex; } diff --git a/src/expression/types.ts b/src/expression/types.ts index 93e4a81f5..a58cbf0d9 100644 --- a/src/expression/types.ts +++ b/src/expression/types.ts @@ -49,8 +49,24 @@ export type VariableAnchorOffsetCollectionTypeT = { export type EvaluationKind = 'constant' | 'source' | 'camera' | 'composite'; -export type Type = NullTypeT | NumberTypeT | StringTypeT | BooleanTypeT | ColorTypeT | ProjectionDefinitionTypeT | ObjectTypeT | ValueTypeT | -ArrayType | ErrorTypeT | CollatorTypeT | FormattedTypeT | PaddingTypeT | NumberArrayTypeT | ColorArrayTypeT | ResolvedImageTypeT | VariableAnchorOffsetCollectionTypeT; +export type Type = + | NullTypeT + | NumberTypeT + | StringTypeT + | BooleanTypeT + | ColorTypeT + | ProjectionDefinitionTypeT + | ObjectTypeT + | ValueTypeT + | ArrayType + | ErrorTypeT + | CollatorTypeT + | FormattedTypeT + | PaddingTypeT + | NumberArrayTypeT + | ColorArrayTypeT + | ResolvedImageTypeT + | VariableAnchorOffsetCollectionTypeT; export interface ArrayType { kind: 'array'; @@ -65,7 +81,9 @@ export const NumberType = {kind: 'number'} as NumberTypeT; export const StringType = {kind: 'string'} as StringTypeT; export const BooleanType = {kind: 'boolean'} as BooleanTypeT; export const ColorType = {kind: 'color'} as ColorTypeT; -export const ProjectionDefinitionType = {kind: 'projectionDefinition'} as ProjectionDefinitionTypeT; +export const ProjectionDefinitionType = { + kind: 'projectionDefinition' +} as ProjectionDefinitionTypeT; export const ObjectType = {kind: 'object'} as ObjectTypeT; export const ValueType = {kind: 'value'} as ValueTypeT; export const ErrorType = {kind: 'error'} as ErrorTypeT; @@ -75,7 +93,9 @@ export const PaddingType = {kind: 'padding'} as PaddingTypeT; export const ColorArrayType = {kind: 'colorArray'} as ColorArrayTypeT; export const NumberArrayType = {kind: 'numberArray'} as NumberArrayTypeT; export const ResolvedImageType = {kind: 'resolvedImage'} as ResolvedImageTypeT; -export const VariableAnchorOffsetCollectionType = {kind: 'variableAnchorOffsetCollection'} as VariableAnchorOffsetCollectionTypeT; +export const VariableAnchorOffsetCollectionType = { + kind: 'variableAnchorOffsetCollection' +} as VariableAnchorOffsetCollectionTypeT; export function array(itemType: T, N?: number | null): ArrayType { return { @@ -88,9 +108,11 @@ export function array(itemType: T, N?: number | null): ArrayType export function typeToString(type: Type): string { if (type.kind === 'array') { const itemType = typeToString(type.itemType); - return typeof type.N === 'number' ? - `array<${itemType}, ${type.N}>` : - type.itemType.kind === 'value' ? 'array' : `array<${itemType}>`; + return typeof type.N === 'number' + ? `array<${itemType}, ${type.N}>` + : type.itemType.kind === 'value' + ? 'array' + : `array<${itemType}>`; } else { return type.kind; } @@ -123,9 +145,12 @@ export function checkSubtype(expected: Type, t: Type): string { // Error is a subtype of every type return null; } else if (expected.kind === 'array') { - if (t.kind === 'array' && - ((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) && - (typeof expected.N !== 'number' || expected.N === t.N)) { + if ( + t.kind === 'array' && + ((t.N === 0 && t.itemType.kind === 'value') || + !checkSubtype(expected.itemType, t.itemType)) && + (typeof expected.N !== 'number' || expected.N === t.N) + ) { return null; } } else if (expected.kind === t.kind) { @@ -142,11 +167,11 @@ export function checkSubtype(expected: Type, t: Type): string { } export function isValidType(provided: Type, allowedTypes: Array): boolean { - return allowedTypes.some(t => t.kind === provided.kind); + return allowedTypes.some((t) => t.kind === provided.kind); } export function isValidNativeType(provided: any, allowedTypes: Array): boolean { - return allowedTypes.some(t => { + return allowedTypes.some((t) => { if (t === 'null') { return provided === null; } else if (t === 'array') { diff --git a/src/expression/types/collator.ts b/src/expression/types/collator.ts index b0d8844d8..1aab35cdd 100644 --- a/src/expression/types/collator.ts +++ b/src/expression/types/collator.ts @@ -4,14 +4,14 @@ export class Collator { collator: Intl.Collator; constructor(caseSensitive: boolean, diacriticSensitive: boolean, locale: string | null) { - if (caseSensitive) - this.sensitivity = diacriticSensitive ? 'variant' : 'case'; - else - this.sensitivity = diacriticSensitive ? 'accent' : 'base'; + if (caseSensitive) this.sensitivity = diacriticSensitive ? 'variant' : 'case'; + else this.sensitivity = diacriticSensitive ? 'accent' : 'base'; this.locale = locale; - this.collator = new Intl.Collator(this.locale ? this.locale : [], - {sensitivity: this.sensitivity, usage: 'search'}); + this.collator = new Intl.Collator(this.locale ? this.locale : [], { + sensitivity: this.sensitivity, + usage: 'search' + }); } compare(lhs: string, rhs: string): number { @@ -21,7 +21,6 @@ export class Collator { resolvedLocale(): string { // We create a Collator without "usage: search" because we don't want // the search options encoded in our result (e.g. "en-u-co-search") - return new Intl.Collator(this.locale ? this.locale : []) - .resolvedOptions().locale; + return new Intl.Collator(this.locale ? this.locale : []).resolvedOptions().locale; } } diff --git a/src/expression/types/color.test.ts b/src/expression/types/color.test.ts index 465daf406..35640a230 100644 --- a/src/expression/types/color.test.ts +++ b/src/expression/types/color.test.ts @@ -3,14 +3,15 @@ import {Color, isSupportedInterpolationColorSpace} from './color'; import {describe, test, expect} from 'vitest'; describe('Color class', () => { - describe('parsing', () => { - test('should parse valid css color strings', () => { expectToMatchColor(Color.parse('RED'), 'rgb(100% 0% 0% / 1)'); expectToMatchColor(Color.parse('#f00C'), 'rgb(100% 0% 0% / .8)'); expectToMatchColor(Color.parse('rgb(0 0 127.5 / 20%)'), 'rgb(0% 0% 50% / .2)'); - expectToMatchColor(Color.parse('hsl(300deg 100% 25.1% / 0.7)'), 'rgb(50.2% 0% 50.2% / .7)'); + expectToMatchColor( + Color.parse('hsl(300deg 100% 25.1% / 0.7)'), + 'rgb(50.2% 0% 50.2% / .7)' + ); }); test('should return undefined when provided with invalid CSS color string', () => { @@ -28,7 +29,6 @@ describe('Color class', () => { const color = new Color(0, 0, 0, 0); expect(Color.parse(color)).toBe(color); }); - }); test('should keep a reference to the original color when alpha=0', () => { @@ -60,7 +60,6 @@ describe('Color class', () => { }); describe('interpolation color space', () => { - test('should recognize supported interpolation color spaces', () => { expect(isSupportedInterpolationColorSpace('rgb')).toBe(true); expect(isSupportedInterpolationColorSpace('hcl')).toBe(true); @@ -76,21 +75,19 @@ describe('Color class', () => { expect(isSupportedInterpolationColorSpace('interpolate-hcl')).toBe(false); expect(isSupportedInterpolationColorSpace('interpolate-lab')).toBe(false); }); - }); describe('interpolate color', () => { - test('should interpolate colors in "rgb" color space', () => { const color = Color.parse('rgba(0,0,255,1)'); const targetColor = Color.parse('rgba(0,255,0,.6)'); const i11nFn = (t: number) => Color.interpolate(color, targetColor, t, 'rgb'); - expectToMatchColor(i11nFn(0.00), 'rgb(0% 0% 100% / 1)'); + expectToMatchColor(i11nFn(0.0), 'rgb(0% 0% 100% / 1)'); expectToMatchColor(i11nFn(0.25), 'rgb(0% 25% 75% / 0.9)'); - expectToMatchColor(i11nFn(0.50), 'rgb(0% 50% 50% / 0.8)'); + expectToMatchColor(i11nFn(0.5), 'rgb(0% 50% 50% / 0.8)'); expectToMatchColor(i11nFn(0.75), 'rgb(0% 75% 25% / 0.7)'); - expectToMatchColor(i11nFn(1.00), 'rgb(0% 100% 0% / 0.6)'); + expectToMatchColor(i11nFn(1.0), 'rgb(0% 100% 0% / 0.6)'); }); test('should interpolate colors in "hcl" color space', () => { @@ -98,11 +95,11 @@ describe('Color class', () => { const targetColor = Color.parse('rgba(0,255,0,.6)'); const i11nFn = (t: number) => Color.interpolate(color, targetColor, t, 'hcl'); - expectToMatchColor(i11nFn(0.00), 'rgb(0% 0% 100% / 1)'); + expectToMatchColor(i11nFn(0.0), 'rgb(0% 0% 100% / 1)'); expectToMatchColor(i11nFn(0.25), 'rgb(0% 49.37% 100% / 0.9)', 4); - expectToMatchColor(i11nFn(0.50), 'rgb(0% 70.44% 100% / 0.8)', 4); + expectToMatchColor(i11nFn(0.5), 'rgb(0% 70.44% 100% / 0.8)', 4); expectToMatchColor(i11nFn(0.75), 'rgb(0% 87.54% 63.18% / 0.7)', 4); - expectToMatchColor(i11nFn(1.00), 'rgb(0% 100% 0% / 0.6)'); + expectToMatchColor(i11nFn(1.0), 'rgb(0% 100% 0% / 0.6)'); }); test('should interpolate colors in "lab" color space', () => { @@ -110,11 +107,11 @@ describe('Color class', () => { const targetColor = Color.parse('rgba(0,255,0,.6)'); const i11nFn = (t: number) => Color.interpolate(color, targetColor, t, 'lab'); - expectToMatchColor(i11nFn(0.00), 'rgb(0% 0% 100% / 1)'); + expectToMatchColor(i11nFn(0.0), 'rgb(0% 0% 100% / 1)'); expectToMatchColor(i11nFn(0.25), 'rgb(39.64% 34.55% 83.36% / 0.9)', 4); - expectToMatchColor(i11nFn(0.50), 'rgb(46.42% 56.82% 65.91% / 0.8)', 4); + expectToMatchColor(i11nFn(0.5), 'rgb(46.42% 56.82% 65.91% / 0.8)', 4); expectToMatchColor(i11nFn(0.75), 'rgb(41.45% 78.34% 45.62% / 0.7)', 4); - expectToMatchColor(i11nFn(1.00), 'rgb(0% 100% 0% / 0.6)'); + expectToMatchColor(i11nFn(1.0), 'rgb(0% 100% 0% / 0.6)'); }); test('should correctly interpolate colors with alpha=0', () => { @@ -122,11 +119,11 @@ describe('Color class', () => { const targetColor = Color.parse('rgba(0,255,0,1)'); const i11nFn = (t: number) => Color.interpolate(color, targetColor, t, 'rgb'); - expectToMatchColor(i11nFn(0.00), 'rgb(0% 0% 0% / 0)'); + expectToMatchColor(i11nFn(0.0), 'rgb(0% 0% 0% / 0)'); expectToMatchColor(i11nFn(0.25), 'rgb(0% 25% 75% / 0.25)'); - expectToMatchColor(i11nFn(0.50), 'rgb(0% 50% 50% / 0.5)'); + expectToMatchColor(i11nFn(0.5), 'rgb(0% 50% 50% / 0.5)'); expectToMatchColor(i11nFn(0.75), 'rgb(0% 75% 25% / 0.75)'); - expectToMatchColor(i11nFn(1.00), 'rgb(0% 100% 0% / 1)'); + expectToMatchColor(i11nFn(1.0), 'rgb(0% 100% 0% / 1)'); }); test('should limit interpolation results to sRGB gamut', () => { @@ -137,12 +134,10 @@ describe('Color class', () => { const i11nFn = (t: number) => Color.interpolate(color, targetColor, t, space); const colorInBetween = i11nFn(0.5); for (const key of ['r', 'g', 'b', 'a'] as const) { - expect(colorInBetween[ key ]).toBeGreaterThanOrEqual(0); - expect(colorInBetween[ key ]).toBeLessThanOrEqual(1); + expect(colorInBetween[key]).toBeGreaterThanOrEqual(0); + expect(colorInBetween[key]).toBeLessThanOrEqual(1); } } }); - }); - }); diff --git a/src/expression/types/color.ts b/src/expression/types/color.ts index 988a0c322..88e944eac 100644 --- a/src/expression/types/color.ts +++ b/src/expression/types/color.ts @@ -11,7 +11,9 @@ export type InterpolationColorSpace = 'rgb' | 'hcl' | 'lab'; * @returns `true` if the specified color space is one of the supported * interpolation color spaces, `false` otherwise */ -export function isSupportedInterpolationColorSpace(colorSpace: string): colorSpace is InterpolationColorSpace { +export function isSupportedInterpolationColorSpace( + colorSpace: string +): colorSpace is InterpolationColorSpace { return colorSpace === 'rgb' || colorSpace === 'hcl' || colorSpace === 'lab'; } @@ -21,7 +23,6 @@ export function isSupportedInterpolationColorSpace(colorSpace: string): colorSpa * @private */ export class Color { - readonly r: number; readonly g: number; readonly b: number; @@ -155,10 +156,15 @@ export class Color { */ toString(): string { const [r, g, b, a] = this.rgb; - return `rgba(${[r, g, b].map(n => Math.round(n * 255)).join(',')},${a})`; + return `rgba(${[r, g, b].map((n) => Math.round(n * 255)).join(',')},${a})`; } - static interpolate(from: Color, to: Color, t: number, spaceKey: InterpolationColorSpace = 'rgb'): Color { + static interpolate( + from: Color, + to: Color, + t: number, + spaceKey: InterpolationColorSpace = 'rgb' + ): Color { switch (spaceKey) { case 'rgb': { const [r, g, b, alpha] = interpolateArray(from.rgb, to.rgb, t); @@ -167,10 +173,10 @@ export class Color { case 'hcl': { const [hue0, chroma0, light0, alphaF] = from.hcl; const [hue1, chroma1, light1, alphaT] = to.hcl; - + // https://github.com/gka/chroma.js/blob/cd1b3c0926c7a85cbdc3b1453b3a94006de91a92/src/interpolator/_hsx.js let hue, chroma; - + if (!isNaN(hue0) && !isNaN(hue1)) { let dh = hue1 - hue0; if (hue1 > hue0 && dh > 180) { @@ -188,12 +194,12 @@ export class Color { } else { hue = NaN; } - + const [r, g, b, alpha] = hclToRgb([ hue, chroma ?? interpolateNumber(chroma0, chroma1, t), interpolateNumber(light0, light1, t), - interpolateNumber(alphaF, alphaT, t), + interpolateNumber(alphaF, alphaT, t) ]); return new Color(r, g, b, alpha, false); } @@ -203,5 +209,4 @@ export class Color { } } } - } diff --git a/src/expression/types/color_array.test.ts b/src/expression/types/color_array.test.ts index 301dfbb58..341c2ac6a 100644 --- a/src/expression/types/color_array.test.ts +++ b/src/expression/types/color_array.test.ts @@ -12,7 +12,10 @@ describe('ColorArray', () => { expect(ColorArray.parse('yellow').values).toEqual([Color.parse('yellow')]); expect(ColorArray.parse([]).values).toEqual([]); expect(ColorArray.parse(['yellow']).values).toEqual([Color.parse('yellow')]); - expect(ColorArray.parse(['yellow', 'blue']).values).toEqual([Color.parse('yellow'), Color.parse('blue')]); + expect(ColorArray.parse(['yellow', 'blue']).values).toEqual([ + Color.parse('yellow'), + Color.parse('blue') + ]); expect(ColorArray.parse([3, 4] as any)).toBeUndefined(); expect(ColorArray.parse(['non-color', 'words'] as any)).toBeUndefined(); @@ -38,6 +41,8 @@ describe('ColorArray', () => { const colorArray = ColorArray.parse(['#00A0AA', '#000000']); const targetColorArray = ColorArray.parse('#AA0000'); - expect(() => {ColorArray.interpolate(colorArray, targetColorArray, 0.5);}).toThrow('colorArray: Arrays have mismatched length (2 vs. 1), cannot interpolate.'); + expect(() => { + ColorArray.interpolate(colorArray, targetColorArray, 0.5); + }).toThrow('colorArray: Arrays have mismatched length (2 vs. 1), cannot interpolate.'); }); }); diff --git a/src/expression/types/color_array.ts b/src/expression/types/color_array.ts index 9d32152ad..711433477 100644 --- a/src/expression/types/color_array.ts +++ b/src/expression/types/color_array.ts @@ -42,7 +42,7 @@ export class ColorArray { return undefined; } const parsed_val = Color.parse(val); - if(!parsed_val) { + if (!parsed_val) { return undefined; } colors.push(parsed_val); @@ -55,13 +55,20 @@ export class ColorArray { return JSON.stringify(this.values); } - static interpolate(from: ColorArray, to: ColorArray, t: number, spaceKey: InterpolationColorSpace = 'rgb'): ColorArray { - const colors = [] as Color[]; + static interpolate( + from: ColorArray, + to: ColorArray, + t: number, + spaceKey: InterpolationColorSpace = 'rgb' + ): ColorArray { + const colors = [] as Color[]; if (from.values.length != to.values.length) { - throw new Error(`colorArray: Arrays have mismatched length (${from.values.length} vs. ${to.values.length}), cannot interpolate.`); + throw new Error( + `colorArray: Arrays have mismatched length (${from.values.length} vs. ${to.values.length}), cannot interpolate.` + ); } - for(let i = 0; i < from.values.length; i++) { - colors.push(Color.interpolate(from.values[i], to.values[i], t, spaceKey)) + for (let i = 0; i < from.values.length; i++) { + colors.push(Color.interpolate(from.values[i], to.values[i], t, spaceKey)); } return new ColorArray(colors); } diff --git a/src/expression/types/color_spaces.test.ts b/src/expression/types/color_spaces.test.ts index e73d09297..7e8130444 100644 --- a/src/expression/types/color_spaces.test.ts +++ b/src/expression/types/color_spaces.test.ts @@ -3,9 +3,7 @@ import {hclToRgb, hslToRgb, labToRgb, rgbToHcl, rgbToLab} from './color_spaces'; import {describe, test} from 'vitest'; describe('color spaces', () => { - describe('LAB color space', () => { - test('should convert colors from sRGB to LAB color space', () => { expectCloseToArray(rgbToLab([0, 0, 0, 1]), [0, 0, 0, 1]); expectCloseToArray(rgbToLab([1, 1, 1, 1]), [100, 0, 0, 1], 4); @@ -20,16 +18,14 @@ describe('color spaces', () => { expectCloseToArray(labToRgb([0, 0, 0, 1]), [0, 0, 0, 1]); expectCloseToArray(labToRgb([100, 0, 0, 1]), [1, 1, 1, 1]); expectCloseToArray(labToRgb([50, 50, 0, 1]), [0.7562, 0.3045, 0.4756, 1], 4); - expectCloseToArray(labToRgb([70, -45, 0, 1]), [0.1079, 0.7556, 0.6640, 1], 4); + expectCloseToArray(labToRgb([70, -45, 0, 1]), [0.1079, 0.7556, 0.664, 1], 4); expectCloseToArray(labToRgb([70, 0, 70, 1]), [0.7663, 0.6636, 0.0558, 1], 4); - expectCloseToArray(labToRgb([55, 0, -60, 1]), [0.1281, 0.5310, 0.9276, 1], 4); + expectCloseToArray(labToRgb([55, 0, -60, 1]), [0.1281, 0.531, 0.9276, 1], 4); expectCloseToArray(labToRgb([29.57, 68.3, -112.03, 1]), [0, 0, 1, 1], 3); }); - }); describe('HCL color space', () => { - test('should convert colors from sRGB to HCL color space', () => { expectCloseToArray(rgbToHcl([0, 0, 0, 1]), [NaN, 0, 0, 1]); expectCloseToArray(rgbToHcl([1, 1, 1, 1]), [NaN, 0, 100, 1], 4); @@ -44,16 +40,14 @@ describe('color spaces', () => { expectCloseToArray(hclToRgb([0, 0, 0, 1]), [0, 0, 0, 1]); expectCloseToArray(hclToRgb([0, 0, 100, 1]), [1, 1, 1, 1]); expectCloseToArray(hclToRgb([0, 50, 50, 1]), [0.7562, 0.3045, 0.4756, 1], 4); - expectCloseToArray(hclToRgb([180, 45, 70, 1]), [0.1079, 0.7556, 0.6640, 1], 4); + expectCloseToArray(hclToRgb([180, 45, 70, 1]), [0.1079, 0.7556, 0.664, 1], 4); expectCloseToArray(hclToRgb([90, 70, 70, 1]), [0.7663, 0.6636, 0.0558, 1], 4); - expectCloseToArray(hclToRgb([270, 60, 55, 1]), [0.1281, 0.5310, 0.9276, 1], 4); + expectCloseToArray(hclToRgb([270, 60, 55, 1]), [0.1281, 0.531, 0.9276, 1], 4); expectCloseToArray(hclToRgb([301.37, 131.21, 29.57, 1]), [0, 0, 1, 1], 3); }); - }); describe('HSL color space', () => { - test('should convert colors from HSL to sRGB color space', () => { expectCloseToArray(hslToRgb([0, 0, 0, 1]), [0, 0, 0, 1]); expectCloseToArray(hslToRgb([0, 100, 0, 1]), [0, 0, 0, 1]); @@ -61,13 +55,19 @@ describe('color spaces', () => { expectCloseToArray(hslToRgb([360, 0, 0, 1]), [0, 0, 0, 1]); expectCloseToArray(hslToRgb([120, 100, 25, 1]), [0, 128 / 255, 0, 1], 2); expectCloseToArray(hslToRgb([120, 30, 50, 0]), [89 / 255, 166 / 255, 89 / 255, 0], 2); - expectCloseToArray(hslToRgb([240, 25, 50, 0.1]), [96 / 255, 96 / 255, 159 / 255, 0.1], 2); - expectCloseToArray(hslToRgb([240, 50, 50, 0.8]), [64 / 255, 64 / 255, 191 / 255, 0.8], 2); + expectCloseToArray( + hslToRgb([240, 25, 50, 0.1]), + [96 / 255, 96 / 255, 159 / 255, 0.1], + 2 + ); + expectCloseToArray( + hslToRgb([240, 50, 50, 0.8]), + [64 / 255, 64 / 255, 191 / 255, 0.8], + 2 + ); expectCloseToArray(hslToRgb([270, 75, 75, 1]), [191 / 255, 143 / 255, 239 / 255, 1], 2); expectCloseToArray(hslToRgb([300, 100, 50, 0.5]), [1, 0, 1, 0.5]); expectCloseToArray(hslToRgb([330, 0, 25, 0.3]), [64 / 255, 64 / 255, 64 / 255, 0.3], 2); }); - }); - }); diff --git a/src/expression/types/color_spaces.ts b/src/expression/types/color_spaces.ts index 31a57f02a..43c965c32 100644 --- a/src/expression/types/color_spaces.ts +++ b/src/expression/types/color_spaces.ts @@ -63,15 +63,15 @@ export function rgbToLab([r, g, b, alpha]: RGBColor): LABColor { } const l = 116 * y - 16; - return [(l < 0) ? 0 : l, 500 * (x - y), 200 * (y - z), alpha]; + return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z), alpha]; } function rgb2xyz(x: number): number { - return (x <= 0.04045) ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); + return x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); } function xyz2lab(t: number): number { - return (t > t3) ? Math.pow(t, 1 / 3) : t / t2 + t0; + return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0; } export function labToRgb([l, a, b, alpha]: LABColor): RGBColor { @@ -85,19 +85,19 @@ export function labToRgb([l, a, b, alpha]: LABColor): RGBColor { return [ xyz2rgb(3.1338561 * x - 1.6168667 * y - 0.4906146 * z), // D50 -> sRGB - xyz2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z), + xyz2rgb(-0.9787684 * x + 1.9161415 * y + 0.033454 * z), xyz2rgb(0.0719453 * x - 0.2289914 * y + 1.4052427 * z), - alpha, + alpha ]; } function xyz2rgb(x: number): number { - x = (x <= 0.00304) ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055; - return (x < 0) ? 0 : (x > 1) ? 1 : x; // clip to 0..1 range + x = x <= 0.00304 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055; + return x < 0 ? 0 : x > 1 ? 1 : x; // clip to 0..1 range } function lab2xyz(t: number): number { - return (t > t1) ? t * t * t : t2 * (t - t0); + return t > t1 ? t * t * t : t2 * (t - t0); } export function rgbToHcl(rgbColor: RGBColor): HCLColor { diff --git a/src/expression/types/formatted.ts b/src/expression/types/formatted.ts index 81780b004..c7f7552f2 100644 --- a/src/expression/types/formatted.ts +++ b/src/expression/types/formatted.ts @@ -2,7 +2,7 @@ import type {Color} from '../../expression/types/color'; import type {ResolvedImage} from '../types/resolved_image'; export const VERTICAL_ALIGN_OPTIONS = ['bottom', 'center', 'top'] as const; -export type VerticalAlign = typeof VERTICAL_ALIGN_OPTIONS[number]; +export type VerticalAlign = (typeof VERTICAL_ALIGN_OPTIONS)[number]; export class FormattedSection { text: string; @@ -12,7 +12,14 @@ export class FormattedSection { textColor: Color | null; verticalAlign: VerticalAlign | null; - constructor(text: string, image: ResolvedImage | null, scale: number | null, fontStack: string | null, textColor: Color | null, verticalAlign: VerticalAlign | null) { + constructor( + text: string, + image: ResolvedImage | null, + scale: number | null, + fontStack: string | null, + textColor: Color | null, + verticalAlign: VerticalAlign | null + ) { this.text = text; this.image = image; this.scale = scale; @@ -35,8 +42,10 @@ export class Formatted { isEmpty(): boolean { if (this.sections.length === 0) return true; - return !this.sections.some(section => section.text.length !== 0 || - (section.image && section.image.name.length !== 0)); + return !this.sections.some( + (section) => + section.text.length !== 0 || (section.image && section.image.name.length !== 0) + ); } static factory(text: Formatted | string): Formatted { @@ -49,6 +58,6 @@ export class Formatted { toString(): string { if (this.sections.length === 0) return ''; - return this.sections.map(section => section.text).join(''); + return this.sections.map((section) => section.text).join(''); } } diff --git a/src/expression/types/parse_css_color.test.ts b/src/expression/types/parse_css_color.test.ts index 64ad379b5..dec878ea5 100644 --- a/src/expression/types/parse_css_color.test.ts +++ b/src/expression/types/parse_css_color.test.ts @@ -3,12 +3,10 @@ import * as colorSpacesModule from './color_spaces'; import {RGBColor} from './color_spaces'; import {describe, test, expect, afterEach, vi} from 'vitest'; describe('parseCssColor', () => { - // by changing the parse function, we can verify external css color parsers against our requirements const parse: (colorToParse: string) => RGBColor | undefined = parseCssColor; describe('color keywords', () => { - test('should parse valid color names', () => { expect(parse('white')).toEqual([1, 1, 1, 1]); expect(parse('black')).toEqual([0, 0, 0, 1]); @@ -34,11 +32,9 @@ describe('parseCssColor', () => { expect(parse('__proto__')).toBeUndefined(); expect(parse('valueOf')).toBeUndefined(); }); - }); describe('RGB hexadecimal notations', () => { - test('should parse valid rgb hex values', () => { // hex 3 expect(parse('#fff')).toEqual([1, 1, 1, 1]); @@ -80,11 +76,9 @@ describe('parseCssColor', () => { expect(parse('fff')).toBeUndefined(); expect(parse('# fff')).toBeUndefined(); }); - }); describe('RGB functions "rgb()" and "rgba()"', () => { - test('should parse valid rgb values', () => { // rgb 0..255 expect(parse('rgb(0 51 0)')).toEqual([0, 0.2, 0, 1]); @@ -204,7 +198,7 @@ describe('parseCssColor', () => { '0%, 50%, 100%,', ', 0%, 50%, 100%', ', 0%, 50%, 100%, 100%', - '0%, 50%, 100%, 100%,', + '0%, 50%, 100%, 100%,' ]; for (const args of invalidArgs) { @@ -219,11 +213,9 @@ describe('parseCssColor', () => { } } }); - }); describe('HSL functions "hsl()" and "hsla()"', () => { - afterEach(() => { vi.resetAllMocks(); }); @@ -232,20 +224,44 @@ describe('parseCssColor', () => { vi.spyOn(colorSpacesModule, 'hslToRgb').mockImplementation((hslColor) => hslColor); expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual([300, 100, 25.1, 1]); - expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual(parseCssColor('hsla(300,100%,25.1%,1)')); - expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual(parseCssColor('hsla(300,100%,25.1%,100%)')); - expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual(parseCssColor('hsl(300 100% 25.1%)')); - expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual(parseCssColor('hsl(300 100% 25.1%/1.0)')); - expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual(parseCssColor('hsl(300.0 100% 25.1% / 100%)')); - expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual(parseCssColor('hsl(300deg 100% 25.1% / 100%)')); + expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual( + parseCssColor('hsla(300,100%,25.1%,1)') + ); + expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual( + parseCssColor('hsla(300,100%,25.1%,100%)') + ); + expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual( + parseCssColor('hsl(300 100% 25.1%)') + ); + expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual( + parseCssColor('hsl(300 100% 25.1%/1.0)') + ); + expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual( + parseCssColor('hsl(300.0 100% 25.1% / 100%)') + ); + expect(parseCssColor('hsl(300,100%,25.1%)')).toEqual( + parseCssColor('hsl(300deg 100% 25.1% / 100%)') + ); expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual([240, 0, 55, 0.2]); - expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual(parseCssColor('hsla(240.0,0%,55%,0.2)')); - expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual(parseCssColor('hsla( 240 ,.0% ,55.0% ,20% )')); - expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual(parseCssColor('hsl(240 0% 55% / 0.2)')); - expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual(parseCssColor('hsl(240 0% 55% / 20%)')); - expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual(parseCssColor('hsl(24e1deg 0e1% 55% / 2e-1)')); - expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual(parseCssColor('hsla(240 -1e-7% 55% / 2e1%)')); + expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual( + parseCssColor('hsla(240.0,0%,55%,0.2)') + ); + expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual( + parseCssColor('hsla( 240 ,.0% ,55.0% ,20% )') + ); + expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual( + parseCssColor('hsl(240 0% 55% / 0.2)') + ); + expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual( + parseCssColor('hsl(240 0% 55% / 20%)') + ); + expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual( + parseCssColor('hsl(24e1deg 0e1% 55% / 2e-1)') + ); + expect(parseCssColor('hsl(240,0%,55%,0.2)')).toEqual( + parseCssColor('hsla(240 -1e-7% 55% / 2e1%)') + ); expect(parseCssColor('hsl(240,0%,55%,0.9)')).toEqual([240, 0, 55, 0.9]); expect(parseCssColor('hsl(240,0%,55%,.0)')).toEqual([240, 0, 55, 0]); @@ -257,7 +273,12 @@ describe('parseCssColor', () => { expect(parse('hsl(0 100% 50%)')).toEqual([1, 0, 0, 1]); expect(parse('hsl(240 100% 50%)')).toEqual([0, 0, 1, 1]); expect(parse('hsl(240 100% 25%)')).toEqual([0, 0, 0.5, 1]); - expect(parse('hsl(273 75% 60%)')).toEqual([expect.closeTo(0.63), expect.closeTo(0.3), 0.9, 1]); + expect(parse('hsl(273 75% 60%)')).toEqual([ + expect.closeTo(0.63), + expect.closeTo(0.3), + 0.9, + 1 + ]); expect(parse('hsl(0 0% 0%)')).toEqual([0, 0, 0, 1]); expect(parse('hsl(0 0% 0% / 0)')).toEqual([0, 0, 0, 0]); @@ -329,7 +350,7 @@ describe('parseCssColor', () => { '0,0,0%', '0 0% 0% /', '0,0%,0%,', - ', 0%,0%,0%', + ', 0%,0%,0%' ]; for (const args of invalidArgs) { @@ -344,7 +365,5 @@ describe('parseCssColor', () => { } } }); - }); - }); diff --git a/src/expression/types/parse_css_color.ts b/src/expression/types/parse_css_color.ts index 770a28318..f6d763709 100644 --- a/src/expression/types/parse_css_color.ts +++ b/src/expression/types/parse_css_color.ts @@ -51,32 +51,33 @@ export function parseCssColor(input: string): RGBColor | undefined { const step = input.length < 6 ? 1 : 2; let i = 1; return [ - parseHex(input.slice(i, i += step)), - parseHex(input.slice(i, i += step)), - parseHex(input.slice(i, i += step)), - parseHex(input.slice(i, i + step) || 'ff'), + parseHex(input.slice(i, (i += step))), + parseHex(input.slice(i, (i += step))), + parseHex(input.slice(i, (i += step))), + parseHex(input.slice(i, i + step) || 'ff') ]; } } // rgb(128 0 0), rgb(50% 0% 0%), rgba(255,0,255,0.6), rgb(255 0 255 / 60%), rgb(100% 0% 100% /.6) if (input.startsWith('rgb')) { - const rgbRegExp = /^rgba?\(\s*([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/; + const rgbRegExp = + /^rgba?\(\s*([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/; const rgbMatch = input.match(rgbRegExp); if (rgbMatch) { const [ - _, // eslint-disable-line @typescript-eslint/no-unused-vars - r, // + _, // eslint-disable-line @typescript-eslint/no-unused-vars + r, // rp, // % (optional) f1, // , (optional) - g, // + g, // gp, // % (optional) f2, // , (optional) - b, // + b, // bp, // % (optional) f3, // ,|/ (optional) - a, // (optional) - ap, // % (optional) + a, // (optional) + ap // % (optional) ] = rgbMatch; const argFormat = [f1 || ' ', f2 || ' ', f3].join(''); @@ -87,15 +88,13 @@ export function parseCssColor(input: string): RGBColor | undefined { argFormat === ',,,' ) { const valFormat = [rp, gp, bp].join(''); - const maxValue = - (valFormat === '%%%') ? 100 : - (valFormat === '') ? 255 : 0; + const maxValue = valFormat === '%%%' ? 100 : valFormat === '' ? 255 : 0; if (maxValue) { const rgba: RGBColor = [ clamp(+r / maxValue, 0, 1), clamp(+g / maxValue, 0, 1), clamp(+b / maxValue, 0, 1), - a ? parseAlpha(+a, ap) : 1, + a ? parseAlpha(+a, ap) : 1 ]; if (validateNumbers(rgba)) { return rgba; @@ -109,19 +108,20 @@ export function parseCssColor(input: string): RGBColor | undefined { } // hsl(120 50% 80%), hsla(120deg,50%,80%,.9), hsl(12e1 50% 80% / 90%) - const hslRegExp = /^hsla?\(\s*([\de.+-]+)(?:deg)?(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/; + const hslRegExp = + /^hsla?\(\s*([\de.+-]+)(?:deg)?(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/; const hslMatch = input.match(hslRegExp); if (hslMatch) { const [ - _, // eslint-disable-line @typescript-eslint/no-unused-vars - h, // + _, // eslint-disable-line @typescript-eslint/no-unused-vars + h, // f1, // , (optional) - s, // + s, // f2, // , (optional) - l, // + l, // f3, // ,|/ (optional) - a, // (optional) - ap, // % (optional) + a, // (optional) + ap // % (optional) ] = hslMatch; const argFormat = [f1 || ' ', f2 || ' ', f3].join(''); @@ -135,7 +135,7 @@ export function parseCssColor(input: string): RGBColor | undefined { +h, clamp(+s, 0, 100), clamp(+l, 0, 100), - a ? parseAlpha(+a, ap) : 1, + a ? parseAlpha(+a, ap) : 1 ]; if (validateNumbers(hsla)) { return hslToRgb(hsla); @@ -151,7 +151,7 @@ function parseHex(hex: string): number { } function parseAlpha(a: number, asPercentage: string | undefined): number { - return clamp(asPercentage ? (a / 100) : a, 0, 1); + return clamp(asPercentage ? a / 100 : a, 0, 1); } function clamp(n: number, min: number, max: number): number { @@ -325,5 +325,5 @@ const namedColors: Record = { white: [255, 255, 255], whitesmoke: [245, 245, 245], yellow: [255, 255, 0], - yellowgreen: [154, 205, 50], + yellowgreen: [154, 205, 50] }; diff --git a/src/expression/types/projection_definition.test.ts b/src/expression/types/projection_definition.test.ts index 335643ecf..34eb244bd 100644 --- a/src/expression/types/projection_definition.test.ts +++ b/src/expression/types/projection_definition.test.ts @@ -32,14 +32,22 @@ describe('Projection class', () => { }); test('should interpolate projections', () => { - const projection = ProjectionDefinition.interpolate('mercator', 'vertical-perspective', 0.5); + const projection = ProjectionDefinition.interpolate( + 'mercator', + 'vertical-perspective', + 0.5 + ); expect(projection.from).toBe('mercator'); expect(projection.to).toBe('vertical-perspective'); expect(projection.transition).toBe(0.5); }); test('should parse projection object', () => { - const projection = ProjectionDefinition.parse({from: 'mercator', to: 'vertical-perspective', transition: 0.5}); + const projection = ProjectionDefinition.parse({ + from: 'mercator', + to: 'vertical-perspective', + transition: 0.5 + }); expect(projection.from).toBe('mercator'); expect(projection.to).toBe('vertical-perspective'); expect(projection.transition).toBe(0.5); @@ -47,6 +55,8 @@ describe('Projection class', () => { test('should serialize projection', () => { const projection = ProjectionDefinition.parse(['mercator', 'vertical-perspective', 0.5]); - expect(JSON.stringify(projection)).toBe('{\"from\":\"mercator\",\"to\":\"vertical-perspective\",\"transition\":0.5}'); + expect(JSON.stringify(projection)).toBe( + '{\"from\":\"mercator\",\"to\":\"vertical-perspective\",\"transition\":0.5}' + ); }); }); diff --git a/src/expression/types/projection_definition.ts b/src/expression/types/projection_definition.ts index 53832cdee..0d33818a3 100644 --- a/src/expression/types/projection_definition.ts +++ b/src/expression/types/projection_definition.ts @@ -3,7 +3,7 @@ export class ProjectionDefinition { readonly to: string; readonly transition: number; - constructor(from: string, to: string, transition: number){ + constructor(from: string, to: string, transition: number) { this.from = from; this.to = to; this.transition = transition; @@ -17,11 +17,22 @@ export class ProjectionDefinition { if (input instanceof ProjectionDefinition) { return input; } - if (Array.isArray(input) && input.length === 3 && typeof input[0] === 'string' && typeof input[1] === 'string' && typeof input[2] === 'number') { + if ( + Array.isArray(input) && + input.length === 3 && + typeof input[0] === 'string' && + typeof input[1] === 'string' && + typeof input[2] === 'number' + ) { return new ProjectionDefinition(input[0], input[1], input[2]); } - if (typeof input === 'object' && typeof input.from === 'string' && typeof input.to === 'string' && typeof input.transition === 'number') { + if ( + typeof input === 'object' && + typeof input.from === 'string' && + typeof input.to === 'string' && + typeof input.transition === 'number' + ) { return new ProjectionDefinition(input.from, input.to, input.transition); } @@ -30,4 +41,4 @@ export class ProjectionDefinition { } return undefined; } -} \ No newline at end of file +} diff --git a/src/expression/types/variable_anchor_offset_collection.test.ts b/src/expression/types/variable_anchor_offset_collection.test.ts index 1f929a4e4..a3c2d92f4 100644 --- a/src/expression/types/variable_anchor_offset_collection.test.ts +++ b/src/expression/types/variable_anchor_offset_collection.test.ts @@ -15,9 +15,13 @@ describe('VariableAnchorOffsetCollection', () => { expect(VariableAnchorOffsetCollection.parse(['top', 'bottom'])).toBeUndefined(); expect(VariableAnchorOffsetCollection.parse(['top', 3] as any)).toBeUndefined(); expect(VariableAnchorOffsetCollection.parse(['Dennis', [2, 2]])).toBeUndefined(); - expect(VariableAnchorOffsetCollection.parse(['top', [2, 2]])).toEqual(new VariableAnchorOffsetCollection(['top', [2, 2]])); + expect(VariableAnchorOffsetCollection.parse(['top', [2, 2]])).toEqual( + new VariableAnchorOffsetCollection(['top', [2, 2]]) + ); expect(VariableAnchorOffsetCollection.parse(['top', [2, 2], 'bottom'])).toBeUndefined(); - expect(VariableAnchorOffsetCollection.parse(['top', [2, 2], 'bottom', [3, 3]])).toEqual(new VariableAnchorOffsetCollection(['top', [2, 2], 'bottom', [3, 3]])); + expect(VariableAnchorOffsetCollection.parse(['top', [2, 2], 'bottom', [3, 3]])).toEqual( + new VariableAnchorOffsetCollection(['top', [2, 2], 'bottom', [3, 3]]) + ); const identity = new VariableAnchorOffsetCollection(['top', [2, 2]]); expect(VariableAnchorOffsetCollection.parse(identity)).toBe(identity); @@ -33,12 +37,26 @@ describe('VariableAnchorOffsetCollection', () => { const parseFn = VariableAnchorOffsetCollection.parse; test('should throw with mismatched endpoints', () => { - expect(() => i11nFn(parseFn(['top', [0, 0]]), parseFn(['bottom', [1, 1]]), 0.5)).toThrow('Cannot interpolate values containing mismatched anchors. from[0]: top, to[0]: bottom'); - expect(() => i11nFn(parseFn(['top', [0, 0]]), parseFn(['top', [1, 1], 'bottom', [2, 2]]), 0.5)).toThrow('Cannot interpolate values of different length. from: ["top",[0,0]], to: ["top",[1,1],"bottom",[2,2]]'); + expect(() => + i11nFn(parseFn(['top', [0, 0]]), parseFn(['bottom', [1, 1]]), 0.5) + ).toThrow( + 'Cannot interpolate values containing mismatched anchors. from[0]: top, to[0]: bottom' + ); + expect(() => + i11nFn(parseFn(['top', [0, 0]]), parseFn(['top', [1, 1], 'bottom', [2, 2]]), 0.5) + ).toThrow( + 'Cannot interpolate values of different length. from: ["top",[0,0]], to: ["top",[1,1],"bottom",[2,2]]' + ); }); test('should interpolate offsets', () => { - expect(i11nFn(parseFn(['top', [0, 0], 'bottom', [2, 2]]), parseFn(['top', [1, 1], 'bottom', [4, 4]]), 0.5).values).toEqual(['top', [0.5, 0.5], 'bottom', [3, 3]]); + expect( + i11nFn( + parseFn(['top', [0, 0], 'bottom', [2, 2]]), + parseFn(['top', [1, 1], 'bottom', [4, 4]]), + 0.5 + ).values + ).toEqual(['top', [0.5, 0.5], 'bottom', [3, 3]]); }); }); }); diff --git a/src/expression/types/variable_anchor_offset_collection.ts b/src/expression/types/variable_anchor_offset_collection.ts index 72685ce57..315a98780 100644 --- a/src/expression/types/variable_anchor_offset_collection.ts +++ b/src/expression/types/variable_anchor_offset_collection.ts @@ -3,7 +3,17 @@ import {interpolateNumber} from '../../util/interpolate-primitives'; import type {VariableAnchorOffsetCollectionSpecification} from '../../types.g'; /** Set of valid anchor positions, as a set for validation */ -const anchors = new Set(['center', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right']); +const anchors = new Set([ + 'center', + 'left', + 'right', + 'top', + 'bottom', + 'top-left', + 'top-right', + 'bottom-left', + 'bottom-right' +]); /** * Utility class to assist managing values for text-variable-anchor-offset property. Create instances from @@ -18,14 +28,14 @@ export class VariableAnchorOffsetCollection { this.values = values.slice(); } - static parse(input?: VariableAnchorOffsetCollectionSpecification | VariableAnchorOffsetCollection): VariableAnchorOffsetCollection | undefined { + static parse( + input?: VariableAnchorOffsetCollectionSpecification | VariableAnchorOffsetCollection + ): VariableAnchorOffsetCollection | undefined { if (input instanceof VariableAnchorOffsetCollection) { return input; } - if (!Array.isArray(input) || - input.length < 1 || - input.length % 2 !== 0) { + if (!Array.isArray(input) || input.length < 1 || input.length % 2 !== 0) { return undefined; } @@ -38,7 +48,12 @@ export class VariableAnchorOffsetCollection { return undefined; } - if (!Array.isArray(offsetValue) || offsetValue.length !== 2 || typeof offsetValue[0] !== 'number' || typeof offsetValue[1] !== 'number') { + if ( + !Array.isArray(offsetValue) || + offsetValue.length !== 2 || + typeof offsetValue[0] !== 'number' || + typeof offsetValue[1] !== 'number' + ) { return undefined; } } @@ -50,29 +65,37 @@ export class VariableAnchorOffsetCollection { return JSON.stringify(this.values); } - static interpolate(from: VariableAnchorOffsetCollection, to: VariableAnchorOffsetCollection, t: number): VariableAnchorOffsetCollection { + static interpolate( + from: VariableAnchorOffsetCollection, + to: VariableAnchorOffsetCollection, + t: number + ): VariableAnchorOffsetCollection { const fromValues = from.values; const toValues = to.values; - + if (fromValues.length !== toValues.length) { - throw new RuntimeError(`Cannot interpolate values of different length. from: ${from.toString()}, to: ${to.toString()}`); + throw new RuntimeError( + `Cannot interpolate values of different length. from: ${from.toString()}, to: ${to.toString()}` + ); } - + const output: VariableAnchorOffsetCollectionSpecification = []; - + for (let i = 0; i < fromValues.length; i += 2) { // Anchor entries must match if (fromValues[i] !== toValues[i]) { - throw new RuntimeError(`Cannot interpolate values containing mismatched anchors. from[${i}]: ${fromValues[i]}, to[${i}]: ${toValues[i]}`); + throw new RuntimeError( + `Cannot interpolate values containing mismatched anchors. from[${i}]: ${fromValues[i]}, to[${i}]: ${toValues[i]}` + ); } output.push(fromValues[i]); - + // Interpolate the offset values for each anchor const [fx, fy] = fromValues[i + 1] as [number, number]; const [tx, ty] = toValues[i + 1] as [number, number]; output.push([interpolateNumber(fx, tx, t), interpolateNumber(fy, ty, t)]); } - + return new VariableAnchorOffsetCollection(output); } } diff --git a/src/expression/values.test.ts b/src/expression/values.test.ts index fbea60dcf..a48dac10e 100644 --- a/src/expression/values.test.ts +++ b/src/expression/values.test.ts @@ -1,4 +1,13 @@ -import {CollatorType, ColorArrayType, ColorType, FormattedType, NumberArrayType, PaddingType, ProjectionDefinitionType, VariableAnchorOffsetCollectionType} from './types'; +import { + CollatorType, + ColorArrayType, + ColorType, + FormattedType, + NumberArrayType, + PaddingType, + ProjectionDefinitionType, + VariableAnchorOffsetCollectionType +} from './types'; import {Collator} from './types/collator'; import {Color} from './types/color'; import {ColorArray} from './types/color_array'; @@ -19,6 +28,8 @@ describe('typeOf', () => { expect(typeOf(Padding.parse(1))).toBe(PaddingType); expect(typeOf(NumberArray.parse(1))).toBe(NumberArrayType); expect(typeOf(ColorArray.parse('red'))).toBe(ColorArrayType); - expect(typeOf(VariableAnchorOffsetCollection.parse(['top', [2, 2]]))).toBe(VariableAnchorOffsetCollectionType); - }) + expect(typeOf(VariableAnchorOffsetCollection.parse(['top', [2, 2]]))).toBe( + VariableAnchorOffsetCollectionType + ); + }); }); diff --git a/src/expression/values.ts b/src/expression/values.ts index 2ad44f4a7..4f858a185 100644 --- a/src/expression/values.ts +++ b/src/expression/values.ts @@ -1,4 +1,3 @@ - import {Color} from './types/color'; import {Collator} from './types/collator'; import {Formatted} from './types/formatted'; @@ -8,35 +7,74 @@ import {ColorArray} from './types/color_array'; import {VariableAnchorOffsetCollection} from './types/variable_anchor_offset_collection'; import {ResolvedImage} from './types/resolved_image'; import {ProjectionDefinition} from './types/projection_definition'; -import {NullType, NumberType, StringType, BooleanType, ColorType, ObjectType, ValueType, CollatorType, FormattedType, ResolvedImageType, array, PaddingType, NumberArrayType, ColorArrayType, VariableAnchorOffsetCollectionType, ProjectionDefinitionType} from './types'; +import { + NullType, + NumberType, + StringType, + BooleanType, + ColorType, + ObjectType, + ValueType, + CollatorType, + FormattedType, + ResolvedImageType, + array, + PaddingType, + NumberArrayType, + ColorArrayType, + VariableAnchorOffsetCollectionType, + ProjectionDefinitionType +} from './types'; import type {Type} from './types'; export function validateRGBA(r: unknown, g: unknown, b: unknown, a?: unknown): string | null { - if (!( - typeof r === 'number' && r >= 0 && r <= 255 && - typeof g === 'number' && g >= 0 && g <= 255 && - typeof b === 'number' && b >= 0 && b <= 255 - )) { + if ( + !( + typeof r === 'number' && + r >= 0 && + r <= 255 && + typeof g === 'number' && + g >= 0 && + g <= 255 && + typeof b === 'number' && + b >= 0 && + b <= 255 + ) + ) { const value = typeof a === 'number' ? [r, g, b, a] : [r, g, b]; return `Invalid rgba value [${value.join(', ')}]: 'r', 'g', and 'b' must be between 0 and 255.`; } - if (!( - typeof a === 'undefined' || (typeof a === 'number' && a >= 0 && a <= 1) - )) { + if (!(typeof a === 'undefined' || (typeof a === 'number' && a >= 0 && a <= 1))) { return `Invalid rgba value [${[r, g, b, a].join(', ')}]: 'a' must be between 0 and 1.`; } return null; } -export type Value = null | string | boolean | number | Color | ProjectionDefinition | Collator | Formatted | Padding | NumberArray | ColorArray | ResolvedImage | VariableAnchorOffsetCollection | ReadonlyArray | { - readonly [x: string]: Value; -}; +export type Value = + | null + | string + | boolean + | number + | Color + | ProjectionDefinition + | Collator + | Formatted + | Padding + | NumberArray + | ColorArray + | ResolvedImage + | VariableAnchorOffsetCollection + | ReadonlyArray + | { + readonly [x: string]: Value; + }; export function isValue(mixed: unknown): boolean { - if (mixed === null || + if ( + mixed === null || typeof mixed === 'string' || typeof mixed === 'boolean' || typeof mixed === 'number' || @@ -48,7 +86,8 @@ export function isValue(mixed: unknown): boolean { mixed instanceof NumberArray || mixed instanceof ColorArray || mixed instanceof VariableAnchorOffsetCollection || - mixed instanceof ResolvedImage) { + mixed instanceof ResolvedImage + ) { return true; } else if (Array.isArray(mixed)) { for (const item of mixed) { @@ -124,7 +163,16 @@ export function valueToString(value: Value) { return ''; } else if (type === 'string' || type === 'number' || type === 'boolean') { return String(value); - } else if (value instanceof Color || value instanceof ProjectionDefinition || value instanceof Formatted || value instanceof Padding || value instanceof NumberArray || value instanceof ColorArray || value instanceof VariableAnchorOffsetCollection || value instanceof ResolvedImage) { + } else if ( + value instanceof Color || + value instanceof ProjectionDefinition || + value instanceof Formatted || + value instanceof Padding || + value instanceof NumberArray || + value instanceof ColorArray || + value instanceof VariableAnchorOffsetCollection || + value instanceof ResolvedImage + ) { return value.toString(); } else { return JSON.stringify(value); diff --git a/src/feature_filter/convert.ts b/src/feature_filter/convert.ts index c1ca8f49f..d7eff0668 100644 --- a/src/feature_filter/convert.ts +++ b/src/feature_filter/convert.ts @@ -1,6 +1,12 @@ import {isExpressionFilter} from './index'; -import type {ExpressionFilterSpecification, ExpressionInputType, ExpressionSpecification, FilterSpecification, LegacyFilterSpecification} from '../types.g'; +import type { + ExpressionFilterSpecification, + ExpressionInputType, + ExpressionSpecification, + FilterSpecification, + LegacyFilterSpecification +} from '../types.g'; type ExpectedTypes = {[_: string]: ExpressionInputType}; @@ -52,13 +58,16 @@ type ExpectedTypes = {[_: string]: ExpressionInputType}; * false (legacy filter semantics) are equivalent: they cause the filter to * produce a `false` result. */ -export function convertFilter(filter: FilterSpecification, expectedTypes: ExpectedTypes = {}): ExpressionFilterSpecification { +export function convertFilter( + filter: FilterSpecification, + expectedTypes: ExpectedTypes = {} +): ExpressionFilterSpecification { if (isExpressionFilter(filter)) return filter; if (!filter) return true; const legacyFilter = filter as LegacyFilterSpecification; const legacyOp = legacyFilter[0]; - if (filter.length <= 1) return (legacyOp !== 'any'); + if (filter.length <= 1) return legacyOp !== 'any'; switch (legacyOp) { case '==': @@ -76,13 +85,15 @@ export function convertFilter(filter: FilterSpecification, expectedTypes: Expect const types = {}; const child = convertFilter(f, types); const typechecks = runtimeTypeChecks(types); - return typechecks === true ? child : ['case', typechecks, child, false] as ExpressionSpecification; + return typechecks === true + ? child + : (['case', typechecks, child, false] as ExpressionSpecification); }); return ['any', ...children]; } case 'all': { const [, ...conditions] = legacyFilter; - const children = conditions.map(f => convertFilter(f, expectedTypes)); + const children = conditions.map((f) => convertFilter(f, expectedTypes)); return children.length > 1 ? ['all', ...children] : children[0]; } case 'none': { @@ -125,7 +136,12 @@ function runtimeTypeChecks(expectedTypes: ExpectedTypes): ExpressionFilterSpecif return ['all', ...conditions]; } -function convertComparisonOp(property: string, value: any, op: string, expectedTypes?: ExpectedTypes | null): ExpressionFilterSpecification { +function convertComparisonOp( + property: string, + value: any, + op: string, + expectedTypes?: ExpectedTypes | null +): ExpressionFilterSpecification { let get; if (property === '$type') { return [op, ['geometry-type'], value] as ExpressionFilterSpecification; @@ -136,7 +152,7 @@ function convertComparisonOp(property: string, value: any, op: string, expectedT } if (expectedTypes && value !== null) { - const type = (typeof value as any); + const type = typeof value as any; expectedTypes[property] = type; } @@ -157,7 +173,11 @@ function convertComparisonOp(property: string, value: any, op: string, expectedT return [op, get, value] as ExpressionFilterSpecification; } -function convertInOp(property: string, values: Array, negate = false): ExpressionFilterSpecification { +function convertInOp( + property: string, + values: Array, + negate = false +): ExpressionFilterSpecification { if (values.length === 0) return negate; let get: ExpressionSpecification; @@ -189,9 +209,9 @@ function convertInOp(property: string, values: Array, negate = false): Expr } if (negate) { - return ['all', ...values.map(v => ['!=', get, v] as ExpressionSpecification)]; + return ['all', ...values.map((v) => ['!=', get, v] as ExpressionSpecification)]; } else { - return ['any', ...values.map(v => ['==', get, v] as ExpressionSpecification)]; + return ['any', ...values.map((v) => ['==', get, v] as ExpressionSpecification)]; } } diff --git a/src/feature_filter/feature_filter.test.ts b/src/feature_filter/feature_filter.test.ts index 60b9f090c..54d89fee0 100644 --- a/src/feature_filter/feature_filter.test.ts +++ b/src/feature_filter/feature_filter.test.ts @@ -19,7 +19,7 @@ describe('filter', () => { }); test('expression, compare two properties', () => { - vi.spyOn(console, 'warn').mockImplementation(() => { }); + vi.spyOn(console, 'warn').mockImplementation(() => {}); const f = featureFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']]]).filter; expect(f({zoom: 0}, {properties: {x: 1, y: 1}} as any as Feature)).toBe(false); expect(f({zoom: 0}, {properties: {x: '1', y: '1'}} as any as Feature)).toBe(true); @@ -29,15 +29,37 @@ describe('filter', () => { }); test('expression, collator comparison', () => { - const caseSensitive = featureFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']], ['collator', {'case-sensitive': true}]]).filter; - expect(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'b'}} as any as Feature)).toBe(false); - expect(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'A'}} as any as Feature)).toBe(false); - expect(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'a'}} as any as Feature)).toBe(true); - - const caseInsensitive = featureFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']], ['collator', {'case-sensitive': false}]]).filter; - expect(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'b'}} as any as Feature)).toBe(false); - expect(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'A'}} as any as Feature)).toBe(true); - expect(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'a'}} as any as Feature)).toBe(true); + const caseSensitive = featureFilter([ + '==', + ['string', ['get', 'x']], + ['string', ['get', 'y']], + ['collator', {'case-sensitive': true}] + ]).filter; + expect(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'b'}} as any as Feature)).toBe( + false + ); + expect(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'A'}} as any as Feature)).toBe( + false + ); + expect(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'a'}} as any as Feature)).toBe( + true + ); + + const caseInsensitive = featureFilter([ + '==', + ['string', ['get', 'x']], + ['string', ['get', 'y']], + ['collator', {'case-sensitive': false}] + ]).filter; + expect(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'b'}} as any as Feature)).toBe( + false + ); + expect(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'A'}} as any as Feature)).toBe( + true + ); + expect(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'a'}} as any as Feature)).toBe( + true + ); }); test('expression, any/all', () => { @@ -75,11 +97,24 @@ describe('filter', () => { expect(() => { featureFilter(['boolean', ['get', 'x']]); }).not.toThrow(); - }); test('expression, within', () => { - const withinFilter = featureFilter(['within', {'type': 'Polygon', 'coordinates': [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]]}]); + const withinFilter = featureFilter([ + 'within', + { + type: 'Polygon', + coordinates: [ + [ + [0, 0], + [5, 0], + [5, 5], + [0, 5], + [0, 0] + ] + ] + } + ]); expect(withinFilter.needGeometry).toBe(true); const canonical = {z: 3, x: 3, y: 3} as ICanonicalTileID; const featureInTile = {} as Feature; @@ -89,11 +124,41 @@ describe('filter', () => { expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); getGeometry(featureInTile, {type: 'Point', coordinates: [5, 5]}, canonical); expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); - getGeometry(featureInTile, {type: 'LineString', coordinates: [[2, 2], [3, 3]]}, canonical); + getGeometry( + featureInTile, + { + type: 'LineString', + coordinates: [ + [2, 2], + [3, 3] + ] + }, + canonical + ); expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(true); - getGeometry(featureInTile, {type: 'LineString', coordinates: [[6, 6], [2, 2]]}, canonical); + getGeometry( + featureInTile, + { + type: 'LineString', + coordinates: [ + [6, 6], + [2, 2] + ] + }, + canonical + ); expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); - getGeometry(featureInTile, {type: 'LineString', coordinates: [[5, 5], [2, 2]]}, canonical); + getGeometry( + featureInTile, + { + type: 'LineString', + coordinates: [ + [5, 5], + [2, 2] + ] + }, + canonical + ); expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); }); @@ -104,7 +169,6 @@ describe('filter', () => { }); legacyFilterTests(featureFilter); - }); describe('getGlobalStateRefs', () => { @@ -137,21 +201,21 @@ describe('legacy filter detection', () => { expect(isExpressionFilter(['in', true, true])).toBeTruthy(); expect(isExpressionFilter(['in', 'red', ['get', 'colors']])).toBeTruthy(); }); - }); describe('convert legacy filters to expressions', () => { beforeEach(() => { - vi.spyOn(console, 'warn').mockImplementation(() => { }); + vi.spyOn(console, 'warn').mockImplementation(() => {}); }); - legacyFilterTests(f => { + legacyFilterTests((f) => { const converted = convertFilter(f); return featureFilter(converted); }); test('mimic legacy type mismatch semantics', () => { - const filter = ['any', + const filter = [ + 'any', ['all', ['>', 'y', 0], ['>', 'y', 0]], ['>', 'x', 0] ] as FilterSpecification; @@ -170,35 +234,14 @@ describe('convert legacy filters to expressions', () => { test('flattens nested, single child all expressions', () => { const filter: FilterSpecification = [ 'all', - [ - 'in', - '$type', - 'Polygon', - 'LineString', - 'Point' - ], - [ - 'all', - ['in', 'type', 'island'] - ] + ['in', '$type', 'Polygon', 'LineString', 'Point'], + ['all', ['in', 'type', 'island']] ]; const expected: FilterSpecification = [ 'all', - [ - 'match', - ['geometry-type'], - ['LineString', 'Point', 'Polygon'], - true, - false - ], - [ - 'match', - ['get', 'type'], - ['island'], - true, - false - ] + ['match', ['geometry-type'], ['LineString', 'Point', 'Polygon'], true, false], + ['match', ['get', 'type'], ['island'], true, false] ]; const converted = convertFilter(filter); @@ -206,28 +249,13 @@ describe('convert legacy filters to expressions', () => { }); test('removes duplicates when outputting match expressions', () => { - const filter = [ - 'in', - '$id', - 1, - 2, - 3, - 2, - 1 - ] as FilterSpecification; + const filter = ['in', '$id', 1, 2, 3, 2, 1] as FilterSpecification; - const expected = [ - 'match', - ['id'], - [1, 2, 3], - true, - false - ]; + const expected = ['match', ['id'], [1, 2, 3], true, false]; const converted = convertFilter(filter); expect(converted).toEqual(expected); }); - }); function legacyFilterTests(createFilterExpr) { @@ -279,7 +307,6 @@ function legacyFilterTests(createFilterExpr) { expect(f({zoom: 0}, {id: 1234})).toBe(true); expect(f({zoom: 0}, {id: '1234'})).toBe(false); expect(f({zoom: 0}, {properties: {id: 1234}})).toBe(false); - }); test('!=, string', () => { @@ -510,7 +537,6 @@ function legacyFilterTests(createFilterExpr) { expect(f1({zoom: 0}, {type: 1})).toBe(true); expect(f1({zoom: 0}, {type: 2})).toBe(true); expect(f1({zoom: 0}, {type: 3})).toBe(true); - }); test('!in, degenerate', () => { @@ -551,7 +577,9 @@ function legacyFilterTests(createFilterExpr) { }); test('!in, large_multiple', () => { - const f = createFilterExpr(['!in', 'foo'].concat(Array.from({length: 2000}).map(Number.call, Number))).filter; + const f = createFilterExpr( + ['!in', 'foo'].concat(Array.from({length: 2000}).map(Number.call, Number)) + ).filter; expect(f({zoom: 0}, {properties: {foo: 0}})).toBe(false); expect(f({zoom: 0}, {properties: {foo: 1}})).toBe(false); expect(f({zoom: 0}, {properties: {foo: 1999}})).toBe(false); @@ -577,7 +605,6 @@ function legacyFilterTests(createFilterExpr) { const f4 = createFilterExpr(['any', ['==', 'foo', 0], ['==', 'foo', 1]]).filter; expect(f4({zoom: 0}, {properties: {foo: 1}})).toBe(true); - }); test('all', () => { @@ -592,7 +619,6 @@ function legacyFilterTests(createFilterExpr) { const f4 = createFilterExpr(['all', ['==', 'foo', 0], ['==', 'foo', 1]]).filter; expect(f4({zoom: 0}, {properties: {foo: 1}})).toBe(false); - }); test('none', () => { @@ -607,7 +633,6 @@ function legacyFilterTests(createFilterExpr) { const f4 = createFilterExpr(['none', ['==', 'foo', 0], ['==', 'foo', 1]]).filter; expect(f4({zoom: 0}, {properties: {foo: 1}})).toBe(false); - }); test('has', () => { diff --git a/src/feature_filter/index.ts b/src/feature_filter/index.ts index 5a90178ac..de9bae553 100644 --- a/src/feature_filter/index.ts +++ b/src/feature_filter/index.ts @@ -29,7 +29,9 @@ export function isExpressionFilter(filter: any): filter is ExpressionFilterSpeci return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type'; case 'in': - return filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2])); + return ( + filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2])) + ); case '!in': case '!has': @@ -42,7 +44,7 @@ export function isExpressionFilter(filter: any): filter is ExpressionFilterSpeci case '>=': case '<': case '<=': - return filter.length !== 3 || (Array.isArray(filter[1]) || Array.isArray(filter[2])); + return filter.length !== 3 || Array.isArray(filter[1]) || Array.isArray(filter[2]); case 'any': case 'all': @@ -59,13 +61,13 @@ export function isExpressionFilter(filter: any): filter is ExpressionFilterSpeci } const filterSpec = { - 'type': 'boolean', - 'default': false, - 'transition': false, + type: 'boolean', + default: false, + transition: false, 'property-type': 'data-driven', - 'expression': { - 'interpolated': false, - 'parameters': ['zoom', 'feature'] + expression: { + interpolated: false, + parameters: ['zoom', 'feature'] } }; @@ -79,7 +81,10 @@ const filterSpec = { * @param [globalState] Global state object to be used for evaluating 'global-state' expressions * @returns filter-evaluating function */ -export function featureFilter(filter: FilterSpecification | void, globalState?: Record): FeatureFilter { +export function featureFilter( + filter: FilterSpecification | void, + globalState?: Record +): FeatureFilter { if (filter === null || filter === undefined) { return {filter: () => true, needGeometry: false, getGlobalStateRefs: () => new Set()}; } @@ -88,13 +93,21 @@ export function featureFilter(filter: FilterSpecification | void, globalState?: filter = convertFilter(filter) as ExpressionFilterSpecification; } - const compiled = createExpression(filter, filterSpec as StylePropertySpecification, globalState); + const compiled = createExpression( + filter, + filterSpec as StylePropertySpecification, + globalState + ); if (compiled.result === 'error') { - throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); + throw new Error(compiled.value.map((err) => `${err.key}: ${err.message}`).join(', ')); } else { const needGeometry = geometryNeeded(filter); return { - filter: (globalProperties: GlobalProperties, feature: Feature, canonical?: ICanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical), + filter: ( + globalProperties: GlobalProperties, + feature: Feature, + canonical?: ICanonicalTileID + ) => compiled.value.evaluate(globalProperties, feature, {}, canonical), needGeometry, getGlobalStateRefs: () => findGlobalStateRefs(compiled.value.expression) }; @@ -118,22 +131,31 @@ function geometryNeeded(filter) { function convertFilter(filter?: Array | null | void): unknown { if (!filter) return true; const op = filter[0]; - if (filter.length <= 1) return (op !== 'any'); + if (filter.length <= 1) return op !== 'any'; const converted = - op === '==' ? convertComparisonOp(filter[1], filter[2], '==') : - op === '!=' ? convertNegation(convertComparisonOp(filter[1], filter[2], '==')) : - op === '<' || - op === '>' || - op === '<=' || - op === '>=' ? convertComparisonOp(filter[1], filter[2], op) : - op === 'any' ? convertDisjunctionOp(filter.slice(1)) : - op === 'all' ? ['all' as unknown].concat(filter.slice(1).map(convertFilter)) : - op === 'none' ? ['all' as unknown].concat(filter.slice(1).map(convertFilter).map(convertNegation)) : - op === 'in' ? convertInOp(filter[1], filter.slice(2)) : - op === '!in' ? convertNegation(convertInOp(filter[1], filter.slice(2))) : - op === 'has' ? convertHasOp(filter[1]) : - op === '!has' ? convertNegation(convertHasOp(filter[1])) : - true; + op === '==' + ? convertComparisonOp(filter[1], filter[2], '==') + : op === '!=' + ? convertNegation(convertComparisonOp(filter[1], filter[2], '==')) + : op === '<' || op === '>' || op === '<=' || op === '>=' + ? convertComparisonOp(filter[1], filter[2], op) + : op === 'any' + ? convertDisjunctionOp(filter.slice(1)) + : op === 'all' + ? ['all' as unknown].concat(filter.slice(1).map(convertFilter)) + : op === 'none' + ? ['all' as unknown].concat( + filter.slice(1).map(convertFilter).map(convertNegation) + ) + : op === 'in' + ? convertInOp(filter[1], filter.slice(2)) + : op === '!in' + ? convertNegation(convertInOp(filter[1], filter.slice(2))) + : op === 'has' + ? convertHasOp(filter[1]) + : op === '!has' + ? convertNegation(convertHasOp(filter[1])) + : true; return converted; } @@ -153,14 +175,16 @@ function convertDisjunctionOp(filters: Array>) { } function convertInOp(property: string, values: Array) { - if (values.length === 0) { return false; } + if (values.length === 0) { + return false; + } switch (property) { case '$type': return ['filter-type-in', ['literal', values]]; case '$id': return ['filter-id-in', ['literal', values]]; default: - if (values.length > 200 && !values.some(v => typeof v !== typeof values[0])) { + if (values.length > 200 && !values.some((v) => typeof v !== typeof values[0])) { return ['filter-in-large', property, ['literal', values.sort(compare)]]; } else { return ['filter-in-small', property, ['literal', values]]; diff --git a/src/format.test.ts b/src/format.test.ts index e1158f71f..a10ddd8be 100644 --- a/src/format.test.ts +++ b/src/format.test.ts @@ -5,26 +5,35 @@ function roundtrip(style) { } describe('format', () => { - test('orders top-level keys', () => { - expect(Object.keys(roundtrip({ - 'layers': [], - 'other': {}, - 'sources': {}, - 'glyphs': '', - 'sprite': '', - 'version': 6 - }))).toEqual(['version', 'sources', 'sprite', 'glyphs', 'layers', 'other']); + expect( + Object.keys( + roundtrip({ + layers: [], + other: {}, + sources: {}, + glyphs: '', + sprite: '', + version: 6 + }) + ) + ).toEqual(['version', 'sources', 'sprite', 'glyphs', 'layers', 'other']); }); test('orders layer keys', () => { - expect(Object.keys(roundtrip({ - 'layers': [{ - 'paint': {}, - 'layout': {}, - 'id': 'id', - 'type': 'type' - }] - }).layers[0])).toEqual(['id', 'type', 'layout', 'paint']); + expect( + Object.keys( + roundtrip({ + layers: [ + { + paint: {}, + layout: {}, + id: 'id', + type: 'type' + } + ] + }).layers[0] + ) + ).toEqual(['id', 'type', 'layout', 'paint']); }); }); diff --git a/src/format.ts b/src/format.ts index 41dd03d5c..0e2ffb33d 100644 --- a/src/format.ts +++ b/src/format.ts @@ -1,4 +1,3 @@ - import {latest} from './reference/latest'; import stringifyPretty from 'json-stringify-pretty-compact'; @@ -47,4 +46,3 @@ export function format(style, space = 2) { return stringifyPretty(style, {indent: space}); } - diff --git a/src/function/convert.ts b/src/function/convert.ts index 896ae46ed..06a439748 100644 --- a/src/function/convert.ts +++ b/src/function/convert.ts @@ -1,4 +1,3 @@ - import type {StylePropertySpecification} from '..'; function convertLiteral(value) { @@ -40,15 +39,13 @@ function convertIdentityFunction(parameters, propertySpec): Array { // legacy function semantics, insert an explicit assertion instead. return propertySpec.type === 'string' ? ['string', get] : get; } else if (propertySpec.type === 'enum') { - return [ - 'match', - get, - Object.keys(propertySpec.values), + return ['match', get, Object.keys(propertySpec.values), get, parameters.default]; + } else { + const expression = [ + propertySpec.type === 'color' ? 'to-color' : propertySpec.type, get, - parameters.default + convertLiteral(parameters.default) ]; - } else { - const expression = [propertySpec.type === 'color' ? 'to-color' : propertySpec.type, get, convertLiteral(parameters.default)]; if (propertySpec.type === 'array') { expression.splice(1, 0, propertySpec.value, propertySpec.length || null); } @@ -58,9 +55,12 @@ function convertIdentityFunction(parameters, propertySpec): Array { function getInterpolateOperator(parameters) { switch (parameters.colorSpace) { - case 'hcl': return 'interpolate-hcl'; - case 'lab': return 'interpolate-lab'; - default: return 'interpolate'; + case 'hcl': + return 'interpolate-hcl'; + case 'lab': + return 'interpolate-lab'; + default: + return 'interpolate'; } } @@ -76,7 +76,7 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops) { zoom, type: parameters.type, property: parameters.property, - default: parameters.default, + default: parameters.default }; featureFunctionStops[zoom] = []; zoomStops.push(zoom); @@ -93,7 +93,11 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops) { const expression = [getInterpolateOperator(parameters), ['linear'], ['zoom']]; for (const z of zoomStops) { - const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]); + const output = convertPropertyFunction( + featureFunctionParameters[z], + propertySpec, + featureFunctionStops[z] + ); appendStopPair(expression, z, output, false); } @@ -102,7 +106,11 @@ function convertZoomAndPropertyFunction(parameters, propertySpec, stops) { const expression = ['step', ['zoom']]; for (const z of zoomStops) { - const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]); + const output = convertPropertyFunction( + featureFunctionParameters[z], + propertySpec, + featureFunctionStops[z] + ); appendStopPair(expression, z, output, true); } @@ -156,12 +164,14 @@ function convertPropertyFunction(parameters, propertySpec, stops) { appendStopPair(expression, stop[0], stop[1], true); } fixupDegenerateStepCurve(expression); - return parameters.default === undefined ? expression : [ - 'case', - ['==', ['typeof', get], 'number'], - expression, - convertLiteral(parameters.default) - ]; + return parameters.default === undefined + ? expression + : [ + 'case', + ['==', ['typeof', get], 'number'], + expression, + convertLiteral(parameters.default) + ]; } else if (type === 'exponential') { const base = parameters.base !== undefined ? parameters.base : 1; const expression = [ @@ -173,12 +183,14 @@ function convertPropertyFunction(parameters, propertySpec, stops) { for (const stop of stops) { appendStopPair(expression, stop[0], stop[1], false); } - return parameters.default === undefined ? expression : [ - 'case', - ['==', ['typeof', get], 'number'], - expression, - convertLiteral(parameters.default) - ]; + return parameters.default === undefined + ? expression + : [ + 'case', + ['==', ['typeof', get], 'number'], + expression, + convertLiteral(parameters.default) + ]; } else { throw new Error(`Unknown property function type ${type}`); } @@ -193,8 +205,11 @@ function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom']) isStep = true; } else if (type === 'exponential') { const base = parameters.base !== undefined ? parameters.base : 1; - expression = [getInterpolateOperator(parameters), base === 1 ? ['linear'] : ['exponential', base], input]; - + expression = [ + getInterpolateOperator(parameters), + base === 1 ? ['linear'] : ['exponential', base], + input + ]; } else { throw new Error(`Unknown zoom function type "${type}"`); } @@ -261,4 +276,3 @@ export function convertTokenString(s: string) { return result; } - diff --git a/src/function/index.test.ts b/src/function/index.test.ts index 555c02e38..ca48ecb89 100644 --- a/src/function/index.test.ts +++ b/src/function/index.test.ts @@ -9,95 +9,126 @@ import {describe, test, expect} from 'vitest'; describe('binary search', () => { test('will eventually terminate.', () => { - const f = createFunction({ - stops: [[9, 10], [17, 11], [17, 11], [18, 13]], - base: 2 - }, { - type: 'number', - 'property-type': 'data-constant', - expression: { - 'interpolated': true, - 'parameters': ['zoom'] - } - }).evaluate; + const f = createFunction( + { + stops: [ + [9, 10], + [17, 11], + [17, 11], + [18, 13] + ], + base: 2 + }, + { + type: 'number', + 'property-type': 'data-constant', + expression: { + interpolated: true, + parameters: ['zoom'] + } + } + ).evaluate; expect(f({zoom: 17}, undefined)).toBe(11); - }); }); describe('exponential function', () => { test('is the default for interpolated properties', () => { - const f = createFunction({ - stops: [[1, 2], [3, 6]], - base: 2 - }, { - type: 'number', - 'property-type': 'data-constant', - expression: { - 'interpolated': true, - 'parameters': ['zoom'] - } - }).evaluate; + const f = createFunction( + { + stops: [ + [1, 2], + [3, 6] + ], + base: 2 + }, + { + type: 'number', + 'property-type': 'data-constant', + expression: { + interpolated: true, + parameters: ['zoom'] + } + } + ).evaluate; expect(f({zoom: 2}, undefined)).toBeCloseTo(30 / 9); - }); test('base', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 2], [3, 6]], - base: 2 - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, 2], + [3, 6] + ], + base: 2 + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toBeCloseTo(2); expect(f({zoom: 1}, undefined)).toBeCloseTo(2); expect(f({zoom: 2}, undefined)).toBeCloseTo(30 / 9); expect(f({zoom: 3}, undefined)).toBeCloseTo(6); expect(f({zoom: 4}, undefined)).toBeCloseTo(6); - }); test('one stop', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 2]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [[1, 2]] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toBe(2); expect(f({zoom: 1}, undefined)).toBe(2); expect(f({zoom: 2}, undefined)).toBe(2); - }); test('two stops', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 2], [3, 6]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, 2], + [3, 6] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toBe(2); expect(f({zoom: 1}, undefined)).toBe(2); expect(f({zoom: 2}, undefined)).toBe(4); expect(f({zoom: 3}, undefined)).toBe(6); expect(f({zoom: 4}, undefined)).toBe(6); - }); test('three stops', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 2], [3, 6], [5, 10]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, 2], + [3, 6], + [5, 10] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toBe(2); expect(f({zoom: 1}, undefined)).toBe(2); @@ -108,16 +139,23 @@ describe('exponential function', () => { expect(f({zoom: 4.5}, undefined)).toBe(9); expect(f({zoom: 5}, undefined)).toBe(10); expect(f({zoom: 6}, undefined)).toBe(10); - }); test('four stops', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 2], [3, 6], [5, 10], [7, 14]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, 2], + [3, 6], + [5, 10], + [7, 14] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toBe(2); expect(f({zoom: 1}, undefined)).toBe(2); @@ -132,7 +170,6 @@ describe('exponential function', () => { expect(f({zoom: 6.5}, undefined)).toBe(13); expect(f({zoom: 7}, undefined)).toBe(14); expect(f({zoom: 8}, undefined)).toBe(14); - }); test('many stops', () => { @@ -152,32 +189,40 @@ describe('exponential function', () => { [12889, 1000000], [40000, 10000000] ]; - const f = createFunction({ - type: 'exponential', - stops - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 2}, undefined)).toBeCloseTo(100); //, 1e-6); expect(f({zoom: 20}, undefined)).toBeCloseTo(133.9622641509434); expect(f({zoom: 607}, undefined)).toBeCloseTo(400); expect(f({zoom: 680}, undefined)).toBeCloseTo(410.7352941176471); - expect(f({zoom: 4927}, undefined)).toBeCloseTo(1000); //86 + expect(f({zoom: 4927}, undefined)).toBeCloseTo(1000); //86 expect(f({zoom: 7300}, undefined)).toBeCloseTo(14779.590419993057); expect(f({zoom: 10000}, undefined)).toBeCloseTo(99125.30371398819); expect(f({zoom: 20000}, undefined)).toBeCloseTo(3360628.527166095); expect(f({zoom: 40000}, undefined)).toBeCloseTo(10000000); - }); test('color', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 'red'], [11, 'blue']] - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, 'red'], + [11, 'blue'] + ] + }, + { + type: 'color' + } + ).evaluate; expectToMatchColor(f({zoom: 0}, undefined), 'rgb(100% 0% 0% / 1)'); expectToMatchColor(f({zoom: 5}, undefined), 'rgb(60% 0% 40% / 1)'); @@ -185,13 +230,19 @@ describe('exponential function', () => { }); test('color hcl colorspace', () => { - const f = createFunction({ - type: 'exponential', - colorSpace: 'hcl', - stops: [[0, 'rgb(36 98 36 / 0.6)'], [10, 'hsl(222,73%,32%)']], - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + colorSpace: 'hcl', + stops: [ + [0, 'rgb(36 98 36 / 0.6)'], + [10, 'hsl(222,73%,32%)'] + ] + }, + { + type: 'color' + } + ).evaluate; expectToMatchColor(f({zoom: 0}, undefined), 'rgb(14.12% 38.43% 14.12% / .6)', 4); expectToMatchColor(f({zoom: 5}, undefined), 'rgb(0.00% 35.13% 43.84% / 0.8)', 4); @@ -199,13 +250,19 @@ describe('exponential function', () => { }); test('color lab colorspace', () => { - const f = createFunction({ - type: 'exponential', - colorSpace: 'lab', - stops: [[0, '#0009'], [10, 'rgba(0,255,255,1)']], - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + colorSpace: 'lab', + stops: [ + [0, '#0009'], + [10, 'rgba(0,255,255,1)'] + ] + }, + { + type: 'color' + } + ).evaluate; expectToMatchColor(f({zoom: 0}, undefined), 'rgb(0% 0% 0% / .6)'); expectToMatchColor(f({zoom: 5}, undefined), 'rgb(14.15% 46.74% 46.63% / 0.8)', 4); @@ -214,23 +271,35 @@ describe('exponential function', () => { test('color invalid colorspace', () => { expect(() => { - createFunction({ - type: 'exponential', - colorSpace: 'invalid', - stops: [[1, [0, 0, 0, 1]], [10, [0, 1, 1, 1]]] - }, { - type: 'color' - }); + createFunction( + { + type: 'exponential', + colorSpace: 'invalid', + stops: [ + [1, [0, 0, 0, 1]], + [10, [0, 1, 1, 1]] + ] + }, + { + type: 'color' + } + ); }).toThrow('Unknown color space: "invalid"'); }); test('exponential interpolation of colorArray', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, ['red', 'blue']], [11, ['blue', 'red']]] - }, { - type: 'colorArray' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, ['red', 'blue']], + [11, ['blue', 'red']] + ] + }, + { + type: 'colorArray' + } + ).evaluate; expect(f({zoom: 5}, undefined).values).toHaveLength(2); expectToMatchColor(f({zoom: 0}, undefined).values[0], 'rgb(100% 0% 0% / 1)'); @@ -242,13 +311,19 @@ describe('exponential function', () => { }); test('exponential interpolation of colorArray (hcl colorspace)', () => { - const f = createFunction({ - type: 'exponential', - colorSpace: 'hcl', - stops: [[0, 'rgb(36 98 36 / 0.6)'], [10, 'hsl(222,73%,32%)']], - }, { - type: 'colorArray' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + colorSpace: 'hcl', + stops: [ + [0, 'rgb(36 98 36 / 0.6)'], + [10, 'hsl(222,73%,32%)'] + ] + }, + { + type: 'colorArray' + } + ).evaluate; expect(f({zoom: 5}, undefined).values).toHaveLength(1); expectToMatchColor(f({zoom: 0}, undefined).values[0], 'rgb(14.12% 38.43% 14.12% / .6)', 4); @@ -257,13 +332,19 @@ describe('exponential function', () => { }); test('exponential interpolation of colorArray (lab colorspace)', () => { - const f = createFunction({ - type: 'exponential', - colorSpace: 'lab', - stops: [[0, '#0009'], [10, 'rgba(0,255,255,1)']], - }, { - type: 'colorArray' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + colorSpace: 'lab', + stops: [ + [0, '#0009'], + [10, 'rgba(0,255,255,1)'] + ] + }, + { + type: 'colorArray' + } + ).evaluate; expect(f({zoom: 5}, undefined).values).toHaveLength(1); expectToMatchColor(f({zoom: 0}, undefined).values[0], 'rgb(0% 0% 0% / .6)'); @@ -273,24 +354,36 @@ describe('exponential function', () => { test('exponential interpolation of colorArray (invalid colorspace)', () => { expect(() => { - createFunction({ - type: 'exponential', - colorSpace: 'invalid', - stops: [[1, [0, 0, 0, 1]], [10, [0, 1, 1, 1]]] - }, { - type: 'colorArray' - }); + createFunction( + { + type: 'exponential', + colorSpace: 'invalid', + stops: [ + [1, [0, 0, 0, 1]], + [10, [0, 1, 1, 1]] + ] + }, + { + type: 'colorArray' + } + ); }).toThrow('Unknown color space: "invalid"'); }); test('exponential interpolation of numberArray', () => { - const f = createFunction({ - type: 'exponential', - colorSpace: 'lab', - stops: [[0, [1, 2]], [10, [3, 4]]], - }, { - type: 'numberArray' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + colorSpace: 'lab', + stops: [ + [0, [1, 2]], + [10, [3, 4]] + ] + }, + { + type: 'numberArray' + } + ).evaluate; expect(f({zoom: 5}, undefined).values).toHaveLength(2); expect(f({zoom: 0}, undefined).values).toEqual([1, 2]); @@ -302,7 +395,10 @@ describe('exponential function', () => { const params = { type: 'exponential', colorSpace: 'lab', - stops: [[1, [0, 0, 0, 1]], [10, [0, 1, 1, 1]]] + stops: [ + [1, [0, 0, 0, 1]], + [10, [0, 1, 1, 1]] + ] }; const paramsCopy = JSON.parse(JSON.stringify(params)); createFunction(params, { @@ -312,12 +408,18 @@ describe('exponential function', () => { }); test('exponential interpolation of padding', () => { - const f = createFunction({ - type: 'exponential', - stops: [[1, 2], [11, [2, 5, 2, 7]]] - }, { - type: 'padding' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + stops: [ + [1, 2], + [11, [2, 5, 2, 7]] + ] + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toEqual(new Padding([2, 2, 2, 2])); expect(f({zoom: 5}, undefined)).toEqual(new Padding([2, 3.2, 2, 4])); @@ -325,82 +427,110 @@ describe('exponential function', () => { }); test('property present', () => { - const f = createFunction({ - property: 'foo', - type: 'exponential', - stops: [[0, 0], [1, 2]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'exponential', + stops: [ + [0, 0], + [1, 2] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 1}})).toBe(2); - }); test('property absent, function default', () => { - const f = createFunction({ - property: 'foo', - type: 'exponential', - stops: [[0, 0], [1, 2]], - default: 3 - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'exponential', + stops: [ + [0, 0], + [1, 2] + ], + default: 3 + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe(3); - }); test('property absent, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'exponential', - stops: [[0, 0], [1, 2]] - }, { - type: 'number', - default: 3 - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'exponential', + stops: [ + [0, 0], + [1, 2] + ] + }, + { + type: 'number', + default: 3 + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe(3); - }); test('property type mismatch, function default', () => { - const f = createFunction({ - property: 'foo', - type: 'exponential', - stops: [[0, 0], [1, 2]], - default: 3 - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'exponential', + stops: [ + [0, 0], + [1, 2] + ], + default: 3 + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe(3); - }); test('property type mismatch, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'exponential', - stops: [[0, 0], [1, 2]] - }, { - type: 'string', - default: 3 - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'exponential', + stops: [ + [0, 0], + [1, 2] + ] + }, + { + type: 'string', + default: 3 + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe(3); - }); test('zoom-and-property function, one stop', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - stops: [[{zoom: 1, value: 1}, 2]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + stops: [[{zoom: 1, value: 1}, 2]] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, {properties: {prop: 0}})).toBe(2); expect(f({zoom: 1}, {properties: {prop: 0}})).toBe(2); @@ -411,22 +541,25 @@ describe('exponential function', () => { expect(f({zoom: 0}, {properties: {prop: 2}})).toBe(2); expect(f({zoom: 1}, {properties: {prop: 2}})).toBe(2); expect(f({zoom: 2}, {properties: {prop: 2}})).toBe(2); - }); test('zoom-and-property function, two stops', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - base: 1, - stops: [ - [{zoom: 1, value: 0}, 0], - [{zoom: 1, value: 2}, 4], - [{zoom: 3, value: 0}, 0], - [{zoom: 3, value: 2}, 12]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + base: 1, + stops: [ + [{zoom: 1, value: 0}, 0], + [{zoom: 1, value: 2}, 4], + [{zoom: 3, value: 0}, 0], + [{zoom: 3, value: 2}, 12] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, {properties: {prop: 1}})).toBe(2); expect(f({zoom: 1}, {properties: {prop: 1}})).toBe(2); @@ -438,104 +571,117 @@ describe('exponential function', () => { expect(f({zoom: 2}, {properties: {prop: 0}})).toBe(0); expect(f({zoom: 2}, {properties: {prop: 2}})).toBe(8); expect(f({zoom: 2}, {properties: {prop: 3}})).toBe(8); - }); test('zoom-and-property function, three stops', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - base: 1, - stops: [ - [{zoom: 1, value: 0}, 0], - [{zoom: 1, value: 2}, 4], - [{zoom: 3, value: 0}, 0], - [{zoom: 3, value: 2}, 12], - [{zoom: 5, value: 0}, 0], - [{zoom: 5, value: 2}, 20]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + base: 1, + stops: [ + [{zoom: 1, value: 0}, 0], + [{zoom: 1, value: 2}, 4], + [{zoom: 3, value: 0}, 0], + [{zoom: 3, value: 2}, 12], + [{zoom: 5, value: 0}, 0], + [{zoom: 5, value: 2}, 20] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, {properties: {prop: 1}})).toBe(2); expect(f({zoom: 1}, {properties: {prop: 1}})).toBe(2); expect(f({zoom: 2}, {properties: {prop: 1}})).toBe(4); - }); test('zoom-and-property function, two stops, fractional zoom', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - base: 1, - stops: [ - [{zoom: 1.9, value: 0}, 4], - [{zoom: 2.1, value: 0}, 8] - ] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + base: 1, + stops: [ + [{zoom: 1.9, value: 0}, 4], + [{zoom: 2.1, value: 0}, 8] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 1.9}, {properties: {prop: 1}})).toBe(4); expect(f({zoom: 2}, {properties: {prop: 1}})).toBe(6); expect(f({zoom: 2.1}, {properties: {prop: 1}})).toBe(8); - }); test('zoom-and-property function, four stops, integer and fractional zooms', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - base: 1, - stops: [ - [{zoom: 1, value: 0}, 0], - [{zoom: 1.5, value: 0}, 1], - [{zoom: 2, value: 0}, 10], - [{zoom: 2.5, value: 0}, 20] - ] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + base: 1, + stops: [ + [{zoom: 1, value: 0}, 0], + [{zoom: 1.5, value: 0}, 1], + [{zoom: 2, value: 0}, 10], + [{zoom: 2.5, value: 0}, 20] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 1}, {properties: {prop: 0}})).toBe(0); expect(f({zoom: 1.5}, {properties: {prop: 0}})).toBe(1); expect(f({zoom: 2}, {properties: {prop: 0}})).toBe(10); expect(f({zoom: 2.5}, {properties: {prop: 0}})).toBe(20); - }); test('zoom-and-property function, no default', () => { // This can happen for fill-outline-color, where the spec has no default. - const f = createFunction({ - type: 'exponential', - property: 'prop', - base: 1, - stops: [ - [{zoom: 0, value: 1}, 'red'], - [{zoom: 1, value: 1}, 'red'] - ] - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + base: 1, + stops: [ + [{zoom: 0, value: 1}, 'red'], + [{zoom: 1, value: 1}, 'red'] + ] + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBeUndefined(); expect(f({zoom: 0.5}, {properties: {}})).toBeUndefined(); expect(f({zoom: 1}, {properties: {}})).toBeUndefined(); - }); test('zoom-and-property function, color', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - stops: [ - [{zoom: 0, value: 0}, 'red'], [{zoom: 1, value: 0}, 'blue'], - [{zoom: 0, value: 1}, 'lime'], [{zoom: 1, value: 1}, 'magenta'], - ], - }, { - type: 'color', - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + stops: [ + [{zoom: 0, value: 0}, 'red'], + [{zoom: 1, value: 0}, 'blue'], + [{zoom: 0, value: 1}, 'lime'], + [{zoom: 1, value: 1}, 'magenta'] + ] + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0.0}, {properties: {prop: 0}})).toEqual(new Color(1, 0, 0, 1)); expect(f({zoom: 1.0}, {properties: {prop: 0}})).toEqual(new Color(0, 0, 1, 1)); @@ -543,80 +689,103 @@ describe('exponential function', () => { }); test('zoom-and-property function, padding', () => { - const f = createFunction({ - type: 'exponential', - property: 'prop', - stops: [ - [{zoom: 0, value: 0}, [2]], [{zoom: 1, value: 0}, [4]], - [{zoom: 0, value: 1}, [6]], [{zoom: 1, value: 1}, [8]], - ], - }, { - type: 'padding', - }).evaluate; + const f = createFunction( + { + type: 'exponential', + property: 'prop', + stops: [ + [{zoom: 0, value: 0}, [2]], + [{zoom: 1, value: 0}, [4]], + [{zoom: 0, value: 1}, [6]], + [{zoom: 1, value: 1}, [8]] + ] + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0.0}, {properties: {prop: 0}})).toEqual(new Padding([2, 2, 2, 2])); expect(f({zoom: 1.0}, {properties: {prop: 0}})).toEqual(new Padding([4, 4, 4, 4])); expect(f({zoom: 0.5}, {properties: {prop: 1}})).toEqual(new Padding([7, 7, 7, 7])); }); - }); describe('interval function', () => { test('is the default for non-interpolated properties', () => { - const f = createFunction({ - stops: [[-1, 11], [0, 111]] - }, { - type: 'number', - 'property-type': 'data-constant', - expression: { - 'interpolated': false, - 'parameters': ['zoom'] + const f = createFunction( + { + stops: [ + [-1, 11], + [0, 111] + ] + }, + { + type: 'number', + 'property-type': 'data-constant', + expression: { + interpolated: false, + parameters: ['zoom'] + } } - }).evaluate; + ).evaluate; expect(f({zoom: -1.5}, undefined)).toBe(11); expect(f({zoom: -0.5}, undefined)).toBe(11); expect(f({zoom: 0}, undefined)).toBe(111); expect(f({zoom: 0.5}, undefined)).toBe(111); - }); test('one stop', () => { - const f = createFunction({ - type: 'interval', - stops: [[0, 11]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'interval', + stops: [[0, 11]] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: -0.5}, undefined)).toBe(11); expect(f({zoom: 0}, undefined)).toBe(11); expect(f({zoom: 0.5}, undefined)).toBe(11); - }); test('two stops', () => { - const f = createFunction({ - type: 'interval', - stops: [[-1, 11], [0, 111]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'interval', + stops: [ + [-1, 11], + [0, 111] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: -1.5}, undefined)).toBe(11); expect(f({zoom: -0.5}, undefined)).toBe(11); expect(f({zoom: 0}, undefined)).toBe(111); expect(f({zoom: 0.5}, undefined)).toBe(111); - }); test('three stops', () => { - const f = createFunction({ - type: 'interval', - stops: [[-1, 11], [0, 111], [1, 1111]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'interval', + stops: [ + [-1, 11], + [0, 111], + [1, 1111] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: -1.5}, undefined)).toBe(11); expect(f({zoom: -0.5}, undefined)).toBe(11); @@ -624,16 +793,23 @@ describe('interval function', () => { expect(f({zoom: 0.5}, undefined)).toBe(111); expect(f({zoom: 1}, undefined)).toBe(1111); expect(f({zoom: 1.5}, undefined)).toBe(1111); - }); test('four stops', () => { - const f = createFunction({ - type: 'interval', - stops: [[-1, 11], [0, 111], [1, 1111], [2, 11111]] - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + type: 'interval', + stops: [ + [-1, 11], + [0, 111], + [1, 1111], + [2, 11111] + ] + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: -1.5}, undefined)).toBe(11); expect(f({zoom: -0.5}, undefined)).toBe(11); @@ -643,30 +819,40 @@ describe('interval function', () => { expect(f({zoom: 1.5}, undefined)).toBe(1111); expect(f({zoom: 2}, undefined)).toBe(11111); expect(f({zoom: 2.5}, undefined)).toBe(11111); - }); test('color', () => { - const f = createFunction({ - type: 'interval', - stops: [[1, 'red'], [11, 'blue']] - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + type: 'interval', + stops: [ + [1, 'red'], + [11, 'blue'] + ] + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toEqual(new Color(1, 0, 0, 1)); expect(f({zoom: 10}, undefined)).toEqual(new Color(1, 0, 0, 1)); expect(f({zoom: 11}, undefined)).toEqual(new Color(0, 0, 1, 1)); - }); test('padding', () => { - const f = createFunction({ - type: 'interval', - stops: [[1, 2], [11, 4]] - }, { - type: 'padding' - }).evaluate; + const f = createFunction( + { + type: 'interval', + stops: [ + [1, 2], + [11, 4] + ] + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0}, undefined)).toEqual(new Padding([2, 2, 2, 2])); expect(f({zoom: 10}, undefined)).toEqual(new Padding([2, 2, 2, 2])); @@ -674,142 +860,201 @@ describe('interval function', () => { }); test('property present', () => { - const f = createFunction({ - property: 'foo', - type: 'interval', - stops: [[0, 'bad'], [1, 'good'], [2, 'bad']] - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'interval', + stops: [ + [0, 'bad'], + [1, 'good'], + [2, 'bad'] + ] + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 1.5}})).toBe('good'); - }); test('property absent, function default', () => { - const f = createFunction({ - property: 'foo', - type: 'interval', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']], - default: 'default' - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'interval', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ], + default: 'default' + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe('default'); - }); test('property absent, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'interval', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']] - }, { - type: 'string', - default: 'default' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'interval', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ] + }, + { + type: 'string', + default: 'default' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe('default'); - }); test('property type mismatch, function default', () => { - const f = createFunction({ - property: 'foo', - type: 'interval', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']], - default: 'default' - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'interval', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ], + default: 'default' + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe('default'); - }); test('property type mismatch, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'interval', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']] - }, { - type: 'string', - default: 'default' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'interval', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ] + }, + { + type: 'string', + default: 'default' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe('default'); - }); - }); describe('categorical function', () => { test('string', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'bad'], [1, 'good'], [2, 'bad']] - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'bad'], + [1, 'good'], + [2, 'bad'] + ] + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 0}})).toBe('bad'); expect(f({zoom: 0}, {properties: {foo: 1}})).toBe('good'); expect(f({zoom: 0}, {properties: {foo: 2}})).toBe('bad'); - }); test('string function default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']], - default: 'default' - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ], + default: 'default' + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe('default'); expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('default'); - }); test('string zoom-and-property function default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[{zoom: 0, value: 'bar'}, 'zero']], - default: 'default' - }, { - type: 'string', - function: 'interval' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [[{zoom: 0, value: 'bar'}, 'zero']], + default: 'default' + }, + { + type: 'string', + function: 'interval' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe('default'); expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('default'); expect(f({zoom: 0}, {properties: {foo: 'bar'}})).toBe('zero'); - }); test('strict type checking', () => { - const numberKeys = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']], - default: 'default' - }, { - type: 'string' - }).evaluate; - - const stringKeys = createFunction({ - property: 'foo', - type: 'categorical', - stops: [['0', 'zero'], ['1', 'one'], ['2', 'two'], ['true', 'yes'], ['false', 'no']], - default: 'default' - }, { - type: 'string' - }).evaluate; + const numberKeys = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ], + default: 'default' + }, + { + type: 'string' + } + ).evaluate; + + const stringKeys = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + ['0', 'zero'], + ['1', 'one'], + ['2', 'two'], + ['true', 'yes'], + ['false', 'no'] + ], + default: 'default' + }, + { + type: 'string' + } + ).evaluate; expect(numberKeys(0 as any, {properties: {foo: '0'}})).toBe('default'); expect(numberKeys(0 as any, {properties: {foo: '1'}})).toBe('default'); @@ -820,418 +1065,519 @@ describe('categorical function', () => { expect(stringKeys(0 as any, {properties: {foo: 1}})).toBe('default'); expect(stringKeys(0 as any, {properties: {foo: false}})).toBe('default'); expect(stringKeys(0 as any, {properties: {foo: true}})).toBe('default'); - }); test('string spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'zero'], [1, 'one'], [2, 'two']] - }, { - type: 'string', - default: 'default' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'zero'], + [1, 'one'], + [2, 'two'] + ] + }, + { + type: 'string', + default: 'default' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe('default'); expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('default'); - }); test('color', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'red'], [1, 'blue']] - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'red'], + [1, 'blue'] + ] + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 0}})).toEqual(new Color(1, 0, 0, 1)); expect(f({zoom: 1}, {properties: {foo: 1}})).toEqual(new Color(0, 0, 1, 1)); - }); test('color function default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'red'], [1, 'blue']], - default: 'lime' - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'red'], + [1, 'blue'] + ], + default: 'lime' + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(0, 1, 0, 1)); expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Color(0, 1, 0, 1)); - }); test('color spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 'red'], [1, 'blue']] - }, { - type: 'color', - default: 'lime' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 'red'], + [1, 'blue'] + ] + }, + { + type: 'color', + default: 'lime' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(0, 1, 0, 1)); expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Color(0, 1, 0, 1)); - }); test('padding', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 2], [1, 4]] - }, { - type: 'padding' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 2], + [1, 4] + ] + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 0}})).toEqual(new Padding([2, 2, 2, 2])); expect(f({zoom: 1}, {properties: {foo: 1}})).toEqual(new Padding([4, 4, 4, 4])); }); test('padding function default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 2], [1, 4]], - default: 6 - }, { - type: 'padding' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 2], + [1, 4] + ], + default: 6 + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([6, 6, 6, 6])); expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Padding([6, 6, 6, 6])); }); test('padding spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[0, 2], [1, 4]] - }, { - type: 'padding', - default: 6 - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [0, 2], + [1, 4] + ] + }, + { + type: 'padding', + default: 6 + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([6, 6, 6, 6])); expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Padding([6, 6, 6, 6])); }); test('boolean', () => { - const f = createFunction({ - property: 'foo', - type: 'categorical', - stops: [[true, 'true'], [false, 'false']] - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'categorical', + stops: [ + [true, 'true'], + [false, 'false'] + ] + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: true}})).toBe('true'); expect(f({zoom: 0}, {properties: {foo: false}})).toBe('false'); - }); - }); describe('identity function', () => { test('number', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'number' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'number' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 1}})).toBe(1); - }); test('number function default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity', - default: 1 - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity', + default: 1 + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe(1); - }); test('number spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'string', - default: 1 - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'string', + default: 1 + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toBe(1); - }); test('color', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'red'}})).toEqual(new Color(1, 0, 0, 1)); expect(f({zoom: 1}, {properties: {foo: 'blue'}})).toEqual(new Color(0, 0, 1, 1)); - }); test('color function default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity', - default: 'red' - }, { - type: 'color' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity', + default: 'red' + }, + { + type: 'color' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(1, 0, 0, 1)); - }); test('color spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'color', - default: 'red' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'color', + default: 'red' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(1, 0, 0, 1)); - }); test('color invalid', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'color', - default: 'red' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'color', + default: 'red' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'invalid'}})).toEqual(new Color(1, 0, 0, 1)); - }); test('padding', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'padding' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Padding([3, 3, 3, 3])); expect(f({zoom: 1}, {properties: {foo: [3, 4]}})).toEqual(new Padding([3, 4, 3, 4])); }); test('padding function default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity', - default: [1, 2, 3, 4] - }, { - type: 'padding' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity', + default: [1, 2, 3, 4] + }, + { + type: 'padding' + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([1, 2, 3, 4])); }); test('padding spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'padding', - default: [1, 2, 3, 4] - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'padding', + default: [1, 2, 3, 4] + } + ).evaluate; expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([1, 2, 3, 4])); }); test('padding invalid', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'padding', - default: [1, 2, 3, 4] - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'padding', + default: [1, 2, 3, 4] + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'invalid'}})).toEqual(new Padding([1, 2, 3, 4])); }); test('colorArray identity', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'colorArray' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'colorArray' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'red'}})).toEqual(new ColorArray([Color.red])); - expect(f({zoom: 1}, {properties: {foo: ['black', 'white']}})).toEqual(new ColorArray([Color.black, Color.white])); + expect(f({zoom: 1}, {properties: {foo: ['black', 'white']}})).toEqual( + new ColorArray([Color.black, Color.white]) + ); }); test('numberArray identity', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'numberArray' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'numberArray' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new NumberArray([3])); expect(f({zoom: 1}, {properties: {foo: [3, 4]}})).toEqual(new NumberArray([3, 4])); }); test('property type mismatch, function default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity', - default: 'default' - }, { - type: 'string' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity', + default: 'default' + }, + { + type: 'string' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 0}})).toBe('default'); - }); test('property type mismatch, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'string', - default: 'default' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'string', + default: 'default' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 0}})).toBe('default'); - }); test('valid enum', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'enum', - values: { - bar: {} + const f = createFunction( + { + property: 'foo', + type: 'identity' }, - default: 'def' - }).evaluate; + { + type: 'enum', + values: { + bar: {} + }, + default: 'def' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'bar'}})).toBe('bar'); - }); test('invalid enum, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'enum', - values: { - bar: {} + const f = createFunction( + { + property: 'foo', + type: 'identity' }, - default: 'def' - }).evaluate; + { + type: 'enum', + values: { + bar: {} + }, + default: 'def' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'baz'}})).toBe('def'); - }); test('invalid type for enum, spec default', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'enum', - values: { - bar: {} + const f = createFunction( + { + property: 'foo', + type: 'identity' }, - default: 'def' - }).evaluate; + { + type: 'enum', + values: { + bar: {} + }, + default: 'def' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('def'); - }); test('formatted', () => { - const f = createFunction({ - property: 'foo', - type: 'identity' - }, { - type: 'formatted' - }).evaluate; + const f = createFunction( + { + property: 'foo', + type: 'identity' + }, + { + type: 'formatted' + } + ).evaluate; expect(f({zoom: 0}, {properties: {foo: 'foo'}})).toEqual(Formatted.fromString('foo')); expect(f({zoom: 1}, {properties: {foo: 'bar'}})).toEqual(Formatted.fromString('bar')); expect(f({zoom: 2}, {properties: {foo: 2}})).toEqual(Formatted.fromString('2')); expect(f({zoom: 3}, {properties: {foo: true}})).toEqual(Formatted.fromString('true')); - }); - }); test('unknown function', () => { - expect(() => createFunction({ - type: 'nonesuch', stops: [[]] - }, { - type: 'string' - })).toThrow(/Unknown function type "nonesuch"/); + expect(() => + createFunction( + { + type: 'nonesuch', + stops: [[]] + }, + { + type: 'string' + } + ) + ).toThrow(/Unknown function type "nonesuch"/); }); describe('kind', () => { test('camera', () => { - const f = createFunction({ - stops: [[1, 1]] - }, { - type: 'number' - }); + const f = createFunction( + { + stops: [[1, 1]] + }, + { + type: 'number' + } + ); expect(f.kind).toBe('camera'); }); test('source', () => { - const f = createFunction({ - stops: [[1, 1]], - property: 'mapbox' - }, { - type: 'number' - }); + const f = createFunction( + { + stops: [[1, 1]], + property: 'mapbox' + }, + { + type: 'number' + } + ); expect(f.kind).toBe('source'); }); test('composite', () => { - const f = createFunction({ - stops: [[{zoom: 1, value: 1}, 1]], - property: 'mapbox' - }, { - type: 'number' - }); + const f = createFunction( + { + stops: [[{zoom: 1, value: 1}, 1]], + property: 'mapbox' + }, + { + type: 'number' + } + ); expect(f.kind).toBe('composite'); }); - }); diff --git a/src/function/index.ts b/src/function/index.ts index 16f60de62..6197444ee 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -14,7 +14,12 @@ import {ObjectType} from '../expression/types'; import {StylePropertySpecification} from '..'; export function isFunction(value) { - return typeof value === 'object' && value !== null && !Array.isArray(value) && typeOf(value) === ObjectType; + return ( + typeof value === 'object' && + value !== null && + !Array.isArray(value) && + typeOf(value) === ObjectType + ); } function identityFunction(x) { @@ -55,7 +60,8 @@ export function createFunction(parameters, propertySpec) { const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object'; const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined; const zoomDependent = zoomAndFeatureDependent || !featureDependent; - const type = parameters.type || (supportsInterpolation(propertySpec) ? 'exponential' : 'interval'); + const type = + parameters.type || (supportsInterpolation(propertySpec) ? 'exponential' : 'interval'); const parseFn = getParseFunction(propertySpec); if (parseFn) { @@ -113,7 +119,10 @@ export function createFunction(parameters, propertySpec) { const featureFunctionStops = []; for (const z of zoomStops) { - featureFunctionStops.push([featureFunctions[z].zoom, createFunction(featureFunctions[z], propertySpec)]); + featureFunctionStops.push([ + featureFunctions[z].zoom, + createFunction(featureFunctions[z], propertySpec) + ]); } const interpolationType = {name: 'linear'}; @@ -121,29 +130,39 @@ export function createFunction(parameters, propertySpec) { kind: 'composite', interpolationType, interpolationFactor: Interpolate.interpolationFactor.bind(undefined, interpolationType), - zoomStops: featureFunctionStops.map(s => s[0]), + zoomStops: featureFunctionStops.map((s) => s[0]), evaluate({zoom}, properties) { - return evaluateExponentialFunction({ - stops: featureFunctionStops, - base: parameters.base - }, propertySpec, zoom).evaluate(zoom, properties); + return evaluateExponentialFunction( + { + stops: featureFunctionStops, + base: parameters.base + }, + propertySpec, + zoom + ).evaluate(zoom, properties); } }; } else if (zoomDependent) { - const interpolationType = type === 'exponential' ? - {name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1} : null; + const interpolationType = + type === 'exponential' + ? {name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1} + : null; return { kind: 'camera', interpolationType, interpolationFactor: Interpolate.interpolationFactor.bind(undefined, interpolationType), - zoomStops: parameters.stops.map(s => s[0]), - evaluate: ({zoom}) => innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType) + zoomStops: parameters.stops.map((s) => s[0]), + evaluate: ({zoom}) => + innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType) }; } else { return { kind: 'source', evaluate(_, feature) { - const value = feature && feature.properties ? feature.properties[parameters.property] : undefined; + const value = + feature && feature.properties + ? feature.properties[parameters.property] + : undefined; if (value === undefined) { return coalesce(parameters.default, propertySpec.default); } @@ -172,7 +191,10 @@ function evaluateIntervalFunction(parameters, propertySpec, input) { if (input <= parameters.stops[0][0]) return parameters.stops[0][1]; if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1]; - const index = findStopLessThanOrEqualTo(parameters.stops.map((stop) => stop[0]), input); + const index = findStopLessThanOrEqualTo( + parameters.stops.map((stop) => stop[0]), + input + ); return parameters.stops[index][1]; } @@ -187,11 +209,16 @@ function evaluateExponentialFunction(parameters, propertySpec, input) { if (input <= parameters.stops[0][0]) return parameters.stops[0][1]; if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1]; - const index = findStopLessThanOrEqualTo(parameters.stops.map((stop) => stop[0]), input); + const index = findStopLessThanOrEqualTo( + parameters.stops.map((stop) => stop[0]), + input + ); const t = interpolationFactor( - input, base, + input, + base, parameters.stops[index][0], - parameters.stops[index + 1][0]); + parameters.stops[index + 1][0] + ); const outputLower = parameters.stops[index][1]; const outputUpper = parameters.stops[index + 1][1]; @@ -235,7 +262,10 @@ function evaluateIdentityFunction(parameters, propertySpec, input) { input = NumberArray.parse(input); break; default: - if (getType(input) !== propertySpec.type && (propertySpec.type !== 'enum' || !propertySpec.values[input])) { + if ( + getType(input) !== propertySpec.type && + (propertySpec.type !== 'enum' || !propertySpec.values[input]) + ) { input = undefined; } } diff --git a/src/group_by_layout.test.ts b/src/group_by_layout.test.ts index b2a46920a..d1eb6a946 100644 --- a/src/group_by_layout.test.ts +++ b/src/group_by_layout.test.ts @@ -5,12 +5,12 @@ import type {LayerSpecification} from './types.g'; describe('group by layout', () => { test('group layers whose ref properties are identical', () => { const a = { - 'id': 'parent', - 'type': 'line' + id: 'parent', + type: 'line' } as LayerSpecification; const b = { - 'id': 'child', - 'type': 'line' + id: 'child', + type: 'line' } as LayerSpecification; expect(groupByLayout([a, b], {})).toEqual([[a, b]]); expect(groupByLayout([a, b], {})[0][0]).toBe(a); @@ -18,49 +18,66 @@ describe('group by layout', () => { }); test('group does not group unrelated layers', () => { - expect(groupByLayout([ - { - 'id': 'parent', - 'type': 'line' - } as LayerSpecification, - { - 'id': 'child', - 'type': 'fill' - } as LayerSpecification - ], {})).toEqual([ - [{ - 'id': 'parent', - 'type': 'line' - }], [{ - 'id': 'child', - 'type': 'fill' - }] + expect( + groupByLayout( + [ + { + id: 'parent', + type: 'line' + } as LayerSpecification, + { + id: 'child', + type: 'fill' + } as LayerSpecification + ], + {} + ) + ).toEqual([ + [ + { + id: 'parent', + type: 'line' + } + ], + [ + { + id: 'child', + type: 'fill' + } + ] ]); }); test('group works even for differing layout key orders', () => { - expect(groupByLayout([ - { - 'id': 'parent', - 'type': 'line', - 'layout': {'a': 1, 'b': 2} - } as any as LayerSpecification, - { - 'id': 'child', - 'type': 'line', - 'layout': {'b': 2, 'a': 1} - } as any as LayerSpecification - ], {})).toEqual([[ - { - 'id': 'parent', - 'type': 'line', - 'layout': {'a': 1, 'b': 2} - }, - { - 'id': 'child', - 'type': 'line', - 'layout': {'b': 2, 'a': 1} - } - ]]); + expect( + groupByLayout( + [ + { + id: 'parent', + type: 'line', + layout: {a: 1, b: 2} + } as any as LayerSpecification, + { + id: 'child', + type: 'line', + layout: {b: 2, a: 1} + } as any as LayerSpecification + ], + {} + ) + ).toEqual([ + [ + { + id: 'parent', + type: 'line', + layout: {a: 1, b: 2} + }, + { + id: 'child', + type: 'line', + layout: {b: 2, a: 1} + } + ] + ]); }); }); diff --git a/src/group_by_layout.ts b/src/group_by_layout.ts index 91674120e..9547639ed 100644 --- a/src/group_by_layout.ts +++ b/src/group_by_layout.ts @@ -1,10 +1,15 @@ - import {refProperties} from './util/ref_properties'; import type {LayerSpecification} from './types.g'; function stringify(obj: any): string { const type = typeof obj; - if (type === 'number' || type === 'boolean' || type === 'string' || obj === undefined || obj === null) + if ( + type === 'number' || + type === 'boolean' || + type === 'string' || + obj === undefined || + obj === null + ) return JSON.stringify(obj); if (Array.isArray(obj)) { @@ -43,18 +48,19 @@ function getKey(layer: LayerSpecification): string { * * @param layers - an array of {@link LayerSpecification}. * @param cachedKeys - an object to keep already calculated keys. - * @returns an array of arrays of {@link LayerSpecification} objects, where each inner array + * @returns an array of arrays of {@link LayerSpecification} objects, where each inner array * contains layers that share the same layout-affecting properties. */ -export function groupByLayout(layers: LayerSpecification[], cachedKeys?: Record): LayerSpecification[][] { +export function groupByLayout( + layers: LayerSpecification[], + cachedKeys?: Record +): LayerSpecification[][] { const groups: Record = {}; for (let i = 0; i < layers.length; i++) { - const k: string = (cachedKeys && cachedKeys[layers[i].id]) || getKey(layers[i]); // update the cache if there is one - if (cachedKeys) - cachedKeys[layers[i].id] = k; + if (cachedKeys) cachedKeys[layers[i].id] = k; let group = groups[k]; if (!group) { diff --git a/src/index.test-d.ts b/src/index.test-d.ts index 6f02ae09d..32627ab80 100644 --- a/src/index.test-d.ts +++ b/src/index.test-d.ts @@ -11,151 +11,319 @@ import type { LineLayerSpecification, RasterLayerSpecification, SkySpecification, - SymbolLayerSpecification, + SymbolLayerSpecification } from './types.g'; describe('style-spec', () => { test('LightSpecification contains *-transition keys for transitionable properties', () => { expectTypeOf<{'position-transition': {duration: 100}}>().toExtend(); expectTypeOf<{'color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'intensity-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{ + 'intensity-transition': {duration: 100}; + }>().toExtend(); }); test('LightSpecification does not contain *-transition keys for untransitionable properties', () => { - expectTypeOf<{'anchor-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{ + 'anchor-transition': {duration: 100}; + }>().not.toExtend(); }); - + test('SkySpecification contains *-transition keys for transitionable properties', () => { expectTypeOf<{'sky-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'horizon-color-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{ + 'horizon-color-transition': {duration: 100}; + }>().toExtend(); expectTypeOf<{'fog-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fog-ground-blend-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'horizon-fog-blend-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'sky-horizon-blend-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'atmosphere-blend-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{ + 'fog-ground-blend-transition': {duration: 100}; + }>().toExtend(); + expectTypeOf<{ + 'horizon-fog-blend-transition': {duration: 100}; + }>().toExtend(); + expectTypeOf<{ + 'sky-horizon-blend-transition': {duration: 100}; + }>().toExtend(); + expectTypeOf<{ + 'atmosphere-blend-transition': {duration: 100}; + }>().toExtend(); }); - + test('BackgroundLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'background-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'background-pattern-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'background-opacity-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'background-color-transition': {duration: 100}}>().toExtend< + BackgroundLayerSpecification['paint'] + >(); + expectTypeOf<{'background-pattern-transition': {duration: 100}}>().toExtend< + BackgroundLayerSpecification['paint'] + >(); + expectTypeOf<{'background-opacity-transition': {duration: 100}}>().toExtend< + BackgroundLayerSpecification['paint'] + >(); }); - + test('CircleLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'circle-radius-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-blur-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-translate-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-stroke-width-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-stroke-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'circle-stroke-opacity-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'circle-radius-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-color-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-blur-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-opacity-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-translate-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-stroke-width-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-stroke-color-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-stroke-opacity-transition': {duration: 100}}>().toExtend< + CircleLayerSpecification['paint'] + >(); }); test('CircleLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'circle-translate-anchor-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'circle-pitch-scale-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'circle-pitch-alignment-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'circle-translate-anchor-transition': {duration: 100}}>().not.toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-pitch-scale-transition': {duration: 100}}>().not.toExtend< + CircleLayerSpecification['paint'] + >(); + expectTypeOf<{'circle-pitch-alignment-transition': {duration: 100}}>().not.toExtend< + CircleLayerSpecification['paint'] + >(); }); - + test('ColorReliefLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'color-relief-opacity-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'color-relief-opacity-transition': {duration: 100}}>().toExtend< + ColorReliefLayerSpecification['paint'] + >(); }); test('ColorReliefLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'color-relief-color-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'color-relief-color-transition': {duration: 100}}>().not.toExtend< + ColorReliefLayerSpecification['paint'] + >(); }); - + test('FillLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'fill-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-outline-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-translate-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-pattern-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'fill-opacity-transition': {duration: 100}}>().toExtend< + FillLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-color-transition': {duration: 100}}>().toExtend< + FillLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-outline-color-transition': {duration: 100}}>().toExtend< + FillLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-translate-transition': {duration: 100}}>().toExtend< + FillLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-pattern-transition': {duration: 100}}>().toExtend< + FillLayerSpecification['paint'] + >(); }); test('FillLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'fill-antialias-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'fill-translate-anchor-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'fill-antialias-transition': {duration: 100}}>().not.toExtend< + FillLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-translate-anchor-transition': {duration: 100}}>().not.toExtend< + FillLayerSpecification['paint'] + >(); }); - + test('FillExtrusionLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'fill-extrusion-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-extrusion-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-extrusion-translate-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-extrusion-pattern-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-extrusion-height-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'fill-extrusion-base-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'fill-extrusion-opacity-transition': {duration: 100}}>().toExtend< + FillExtrusionLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-extrusion-color-transition': {duration: 100}}>().toExtend< + FillExtrusionLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-extrusion-translate-transition': {duration: 100}}>().toExtend< + FillExtrusionLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-extrusion-pattern-transition': {duration: 100}}>().toExtend< + FillExtrusionLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-extrusion-height-transition': {duration: 100}}>().toExtend< + FillExtrusionLayerSpecification['paint'] + >(); + expectTypeOf<{'fill-extrusion-base-transition': {duration: 100}}>().toExtend< + FillExtrusionLayerSpecification['paint'] + >(); }); test('FillExtrusionLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'fill-extrusion-translate-anchor-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'fill-extrusion-vertical-gradient-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{ + 'fill-extrusion-translate-anchor-transition': {duration: 100}; + }>().not.toExtend(); + expectTypeOf<{ + 'fill-extrusion-vertical-gradient-transition': {duration: 100}; + }>().not.toExtend(); }); - + test('HeatmapLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'heatmap-radius-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'heatmap-intensity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'heatmap-opacity-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'heatmap-radius-transition': {duration: 100}}>().toExtend< + HeatmapLayerSpecification['paint'] + >(); + expectTypeOf<{'heatmap-intensity-transition': {duration: 100}}>().toExtend< + HeatmapLayerSpecification['paint'] + >(); + expectTypeOf<{'heatmap-opacity-transition': {duration: 100}}>().toExtend< + HeatmapLayerSpecification['paint'] + >(); }); test('HeatmapLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'heatmap-weight-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'heatmap-color-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'heatmap-weight-transition': {duration: 100}}>().not.toExtend< + HeatmapLayerSpecification['paint'] + >(); + expectTypeOf<{'heatmap-color-transition': {duration: 100}}>().not.toExtend< + HeatmapLayerSpecification['paint'] + >(); }); - + test('HillshadeLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'hillshade-exaggeration-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'hillshade-shadow-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'hillshade-highlight-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'hillshade-accent-color-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'hillshade-exaggeration-transition': {duration: 100}}>().toExtend< + HillshadeLayerSpecification['paint'] + >(); + expectTypeOf<{'hillshade-shadow-color-transition': {duration: 100}}>().toExtend< + HillshadeLayerSpecification['paint'] + >(); + expectTypeOf<{'hillshade-highlight-color-transition': {duration: 100}}>().toExtend< + HillshadeLayerSpecification['paint'] + >(); + expectTypeOf<{'hillshade-accent-color-transition': {duration: 100}}>().toExtend< + HillshadeLayerSpecification['paint'] + >(); }); test('HillshadeLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'hillshade-illumination-direction-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'hillshade-illumination-altitude-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'hillshade-illumination-anchor-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'hillshade-method-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{ + 'hillshade-illumination-direction-transition': {duration: 100}; + }>().not.toExtend(); + expectTypeOf<{ + 'hillshade-illumination-altitude-transition': {duration: 100}; + }>().not.toExtend(); + expectTypeOf<{ + 'hillshade-illumination-anchor-transition': {duration: 100}; + }>().not.toExtend(); + expectTypeOf<{'hillshade-method-transition': {duration: 100}}>().not.toExtend< + HillshadeLayerSpecification['paint'] + >(); }); - + test('LineLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'line-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-translate-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-width-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-gap-width-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-offset-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-blur-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-dasharray-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'line-pattern-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'line-opacity-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-color-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-translate-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-width-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-gap-width-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-offset-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-blur-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-dasharray-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-pattern-transition': {duration: 100}}>().toExtend< + LineLayerSpecification['paint'] + >(); }); test('LineLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'line-translate-anchor-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'line-gradient-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'line-translate-anchor-transition': {duration: 100}}>().not.toExtend< + LineLayerSpecification['paint'] + >(); + expectTypeOf<{'line-gradient-transition': {duration: 100}}>().not.toExtend< + LineLayerSpecification['paint'] + >(); }); - + test('RasterLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'raster-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'raster-hue-rotate-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'raster-brightness-min-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'raster-brightness-max-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'raster-saturation-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'raster-contrast-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'raster-opacity-transition': {duration: 100}}>().toExtend< + RasterLayerSpecification['paint'] + >(); + expectTypeOf<{'raster-hue-rotate-transition': {duration: 100}}>().toExtend< + RasterLayerSpecification['paint'] + >(); + expectTypeOf<{'raster-brightness-min-transition': {duration: 100}}>().toExtend< + RasterLayerSpecification['paint'] + >(); + expectTypeOf<{'raster-brightness-max-transition': {duration: 100}}>().toExtend< + RasterLayerSpecification['paint'] + >(); + expectTypeOf<{'raster-saturation-transition': {duration: 100}}>().toExtend< + RasterLayerSpecification['paint'] + >(); + expectTypeOf<{'raster-contrast-transition': {duration: 100}}>().toExtend< + RasterLayerSpecification['paint'] + >(); }); test('RasterLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'raster-resampling-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'raster-fade-duration-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'raster-resampling-transition': {duration: 100}}>().not.toExtend< + RasterLayerSpecification['paint'] + >(); + expectTypeOf<{'raster-fade-duration-transition': {duration: 100}}>().not.toExtend< + RasterLayerSpecification['paint'] + >(); }); - + test('SymbolLayerSpecification contains *-transition keys for transitionable paint properties', () => { - expectTypeOf<{'icon-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'icon-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'icon-halo-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'icon-halo-width-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'icon-halo-blur-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'icon-translate-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'text-opacity-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'text-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'text-halo-color-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'text-halo-width-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'text-halo-blur-transition': {duration: 100}}>().toExtend(); - expectTypeOf<{'text-translate-transition': {duration: 100}}>().toExtend(); + expectTypeOf<{'icon-opacity-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'icon-color-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'icon-halo-color-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'icon-halo-width-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'icon-halo-blur-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'icon-translate-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-opacity-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-color-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-halo-color-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-halo-width-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-halo-blur-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-translate-transition': {duration: 100}}>().toExtend< + SymbolLayerSpecification['paint'] + >(); }); test('SymbolLayerSpecification does not contain *-transition keys for untransitionable paint properties', () => { - expectTypeOf<{'icon-translate-anchor-transition': {duration: 100}}>().not.toExtend(); - expectTypeOf<{'text-translate-anchor-transition': {duration: 100}}>().not.toExtend(); + expectTypeOf<{'icon-translate-anchor-transition': {duration: 100}}>().not.toExtend< + SymbolLayerSpecification['paint'] + >(); + expectTypeOf<{'text-translate-anchor-transition': {duration: 100}}>().not.toExtend< + SymbolLayerSpecification['paint'] + >(); }); }); diff --git a/src/index.test.ts b/src/index.test.ts index 9f1531cf9..52946afdd 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,4 +1,3 @@ - import * as spec from '.'; import {describe, test, expect} from 'vitest'; @@ -51,10 +50,21 @@ describe('style-spec', () => { function validSchema(k, v, obj, ref, version, kind) { const scalar = ['boolean', 'string', 'number']; - const types = Object.keys(ref).concat(['boolean', 'string', 'number', - 'array', 'enum', 'color', '*', + const types = Object.keys(ref).concat([ + 'boolean', + 'string', + 'number', + 'array', + 'enum', + 'color', + '*', // new in v8 - 'opacity', 'translate-array', 'dash-array', 'offset-array', 'font-array', 'field-template', + 'opacity', + 'translate-array', + 'dash-array', + 'offset-array', + 'font-array', + 'field-template', // new enums in v8 'line-cap-enum', 'line-join-enum', @@ -117,13 +127,17 @@ function validSchema(k, v, obj, ref, version, kind) { // objects (>=v8) or scalars (<=v7). If objects, check that doc key // (if present) is a string. if (obj.type === 'enum') { - const values = (ref.$version >= 8 ? Object.keys(obj.values) : obj.values); - expect(Array.isArray(values) && values.every((v) => { - return scalar.indexOf(typeof v) !== -1; - })).toBeTruthy(); + const values = ref.$version >= 8 ? Object.keys(obj.values) : obj.values; + expect( + Array.isArray(values) && + values.every((v) => { + return scalar.indexOf(typeof v) !== -1; + }) + ).toBeTruthy(); if (ref.$version >= 8) { for (const v in obj.values) { - if (Array.isArray(obj.values) === false) { // skips $root.version + if (Array.isArray(obj.values) === false) { + // skips $root.version if (obj.values[v].doc !== undefined) { expect('string').toBe(typeof obj.values[v].doc); expect(kind).not.toBe('min'); @@ -165,7 +179,9 @@ function validSchema(k, v, obj, ref, version, kind) { if (obj.function !== undefined) { expect(ref.$version < 8).toBeTruthy(); if (ref.$version >= 7) { - expect(true).toBe(['interpolated', 'piecewise-constant'].indexOf(obj.function) >= 0); + expect(true).toBe( + ['interpolated', 'piecewise-constant'].indexOf(obj.function) >= 0 + ); } else { expect('boolean').toBe(typeof obj.function); } @@ -174,9 +190,12 @@ function validSchema(k, v, obj, ref, version, kind) { expect(ref['property-type'][obj['property-type']]).toBeTruthy(); expect('boolean').toBe(typeof expression.interpolated); expect(true).toBe(Array.isArray(expression.parameters)); - if (obj['property-type'] !== 'color-ramp') expect(true).toBe( - expression.parameters.every(k => k === 'zoom' || k === 'feature' || k === 'feature-state') - ); + if (obj['property-type'] !== 'color-ramp') + expect(true).toBe( + expression.parameters.every( + (k) => k === 'zoom' || k === 'feature' || k === 'feature-state' + ) + ); } // schema key required checks @@ -196,7 +215,14 @@ function validSchema(k, v, obj, ref, version, kind) { } else if (Array.isArray(obj)) { obj.forEach((child, j) => { if (typeof child === 'string' && scalar.indexOf(child) !== -1) return; - validSchema(`${k}[${j}]`, v, typeof child === 'string' ? ref[child] : child, ref, undefined, undefined); + validSchema( + `${k}[${j}]`, + v, + typeof child === 'string' ? ref[child] : child, + ref, + undefined, + undefined + ); }); // Container object. } else if (typeof obj === 'object') { diff --git a/src/index.ts b/src/index.ts index 8aa02fb5a..311df606d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,23 @@ import {derefLayers} from './deref'; import {diff} from './diff'; import {ValidationError} from './error/validation_error'; import {ParsingError} from './error/parsing_error'; -import {FeatureState, StyleExpression, isExpression, isZoomExpression, createExpression, createPropertyExpression, normalizePropertyExpression, ZoomConstantExpression, ZoomDependentExpression, StylePropertyFunction, Feature, GlobalProperties, SourceExpression, CompositeExpression, StylePropertyExpression} from './expression'; +import { + FeatureState, + StyleExpression, + isExpression, + isZoomExpression, + createExpression, + createPropertyExpression, + normalizePropertyExpression, + ZoomConstantExpression, + ZoomDependentExpression, + StylePropertyFunction, + Feature, + GlobalProperties, + SourceExpression, + CompositeExpression, + StylePropertyExpression +} from './expression'; import {featureFilter, isExpressionFilter} from './feature_filter'; import {convertFilter} from './feature_filter/convert'; @@ -22,7 +38,14 @@ import {ResolvedImage} from './expression/types/resolved_image'; import {supportsPropertyExpression} from './util/properties'; import {IMercatorCoordinate, ICanonicalTileID, ILngLat, ILngLatLike} from './tiles_and_coordinates'; import {EvaluationContext} from './expression/evaluation_context'; -import {FormattedType, NullType, Type, typeToString, ColorType, ProjectionDefinitionType} from './expression/types'; +import { + FormattedType, + NullType, + Type, + typeToString, + ColorType, + ProjectionDefinitionType +} from './expression/types'; import {expressions} from './expression/definitions'; import {Interpolate} from './expression/definitions/interpolate'; @@ -36,101 +59,128 @@ import {typeOf} from './expression/values'; import {FormatExpression} from './expression/definitions/format'; import {Literal} from './expression/definitions/literal'; import {CompoundExpression} from './expression/compound_expression'; -import {ColorSpecification, PaddingSpecification, NumberArraySpecification, ColorArraySpecification, ProjectionDefinitionSpecification, VariableAnchorOffsetCollectionSpecification} from './types.g'; +import { + ColorSpecification, + PaddingSpecification, + NumberArraySpecification, + ColorArraySpecification, + ProjectionDefinitionSpecification, + VariableAnchorOffsetCollectionSpecification +} from './types.g'; import {format} from './format'; import {validate} from './validate/validate'; import {migrate} from './migrate'; import {classifyRings} from './util/classify_rings'; import {ProjectionDefinition} from './expression/types/projection_definition'; -type ExpressionType = 'data-driven' | 'cross-faded' | 'cross-faded-data-driven' | 'color-ramp' | 'data-constant' | 'constant'; -type ExpressionParameters = Array<'zoom' | 'feature' | 'feature-state' | 'heatmap-density' | 'elevation' | 'line-progress'>; +type ExpressionType = + | 'data-driven' + | 'cross-faded' + | 'cross-faded-data-driven' + | 'color-ramp' + | 'data-constant' + | 'constant'; +type ExpressionParameters = Array< + 'zoom' | 'feature' | 'feature-state' | 'heatmap-density' | 'elevation' | 'line-progress' +>; type ExpressionSpecificationDefinition = { interpolated: boolean; parameters: ExpressionParameters; }; -export type StylePropertySpecification = { - type: 'number'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: number; -} | { - type: 'string'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: string; - tokens?: boolean; -} | { - type: 'boolean'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: boolean; -} | { - type: 'enum'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - values: {[_: string]: {}}; - transition: boolean; - default?: string; -} | { - type: 'color'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: ColorSpecification; - overridable: boolean; -} | { - type: 'array'; - value: 'number'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - length?: number; - transition: boolean; - default?: Array; -} | { - type: 'array'; - value: 'string'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - length?: number; - transition: boolean; - default?: Array; -} | { - type: 'padding'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: PaddingSpecification; -} | { - type: 'numberArray'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: NumberArraySpecification; -} | { - type: 'colorArray'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: ColorArraySpecification; -} | { - type: 'variableAnchorOffsetCollection'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: VariableAnchorOffsetCollectionSpecification; -} | { - type: 'projectionDefinition'; - 'property-type': ExpressionType; - expression?: ExpressionSpecificationDefinition; - transition: boolean; - default?: ProjectionDefinitionSpecification; -}; +export type StylePropertySpecification = + | { + type: 'number'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: number; + } + | { + type: 'string'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: string; + tokens?: boolean; + } + | { + type: 'boolean'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: boolean; + } + | { + type: 'enum'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + values: {[_: string]: {}}; + transition: boolean; + default?: string; + } + | { + type: 'color'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: ColorSpecification; + overridable: boolean; + } + | { + type: 'array'; + value: 'number'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + length?: number; + transition: boolean; + default?: Array; + } + | { + type: 'array'; + value: 'string'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + length?: number; + transition: boolean; + default?: Array; + } + | { + type: 'padding'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: PaddingSpecification; + } + | { + type: 'numberArray'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: NumberArraySpecification; + } + | { + type: 'colorArray'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: ColorArraySpecification; + } + | { + type: 'variableAnchorOffsetCollection'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: VariableAnchorOffsetCollectionSpecification; + } + | { + type: 'projectionDefinition'; + 'property-type': ExpressionType; + expression?: ExpressionSpecificationDefinition; + transition: boolean; + default?: ProjectionDefinitionSpecification; + }; const expression = { StyleExpression, @@ -142,7 +192,7 @@ const expression = { isExpression, isExpressionFilter, isZoomExpression, - normalizePropertyExpression, + normalizePropertyExpression }; const styleFunction = { @@ -187,9 +237,7 @@ export { StylePropertyExpression, ZoomDependentExpression, FormatExpression, - latest, - validateStyleMin, groupByLayout, emptyStyle, @@ -201,7 +249,8 @@ export { supportsPropertyExpression, convertFunction, createExpression, - isFunction, createFunction, + isFunction, + createFunction, createPropertyExpression, convertFilter, featureFilter, @@ -211,7 +260,6 @@ export { validate, migrate, classifyRings, - ProjectionDefinitionType, ColorType, interpolateFactory as interpolates, @@ -221,5 +269,5 @@ export { visit, expressions, expression, - FormattedType, + FormattedType }; diff --git a/src/migrate.test.ts b/src/migrate.test.ts index 4d05a26dc..21965eab2 100644 --- a/src/migrate.test.ts +++ b/src/migrate.test.ts @@ -25,11 +25,13 @@ describe('migrate', () => { test('converts token strings to expressions', () => { const migrated = migrate({ version: 8, - layers: [{ - id: '1', - type: 'symbol', - layout: {'text-field': 'a{x}', 'icon-image': '{y}'} - }] + layers: [ + { + id: '1', + type: 'symbol', + layout: {'text-field': 'a{x}', 'icon-image': '{y}'} + } + ] } as any); expect(migrated.layers[0].layout['text-field']).toEqual(['concat', 'a', ['get', 'x']]); expect(migrated.layers[0].layout['icon-image']).toEqual(['to-string', ['get', 'y']]); @@ -38,25 +40,34 @@ describe('migrate', () => { test('converts stop functions to expressions', () => { const migrated = migrate({ version: 8, - layers: [{ - id: '1', - type: 'background', - paint: { - 'background-opacity': { - base: 1.0, - stops: [[0, 1], [10, 0.72]] + layers: [ + { + id: '1', + type: 'background', + paint: { + 'background-opacity': { + base: 1.0, + stops: [ + [0, 1], + [10, 0.72] + ] + } } - } - }, { - id: '2', - type: 'background', - paint: { - 'background-opacity': { - base: 1.0, - stops: [[0, [1, 2]], [10, [0.72, 0.98]]] + }, + { + id: '2', + type: 'background', + paint: { + 'background-opacity': { + base: 1.0, + stops: [ + [0, [1, 2]], + [10, [0.72, 0.98]] + ] + } } } - }] + ] } as any); expect(migrated.layers[0].paint['background-opacity']).toEqual([ 'interpolate', @@ -87,20 +98,22 @@ describe('migrate', () => { type: 'vector' } }, - layers: [{ - id: '1', - source: 'maplibre', - 'source-layer': 'labels', - type: 'symbol', - layout: { - 'icon-image': { - base: 1, - type: 'categorical', - property: 'type', - stops: [['park', 'some-icon']] + layers: [ + { + id: '1', + source: 'maplibre', + 'source-layer': 'labels', + type: 'symbol', + layout: { + 'icon-image': { + base: 1, + type: 'categorical', + property: 'type', + stops: [['park', 'some-icon']] + } } } - }] + ] } as any); expect(migrated.layers[0].layout['icon-image']).toEqual([ 'match', @@ -116,28 +129,38 @@ describe('migrate', () => { const migrated = migrate({ version: 8, sources: {}, - layers: [{ - id: '1', - type: 'fill', - source: 'vector', - paint: { - 'fill-color': 'hsl(100,0.3,.2)', - 'fill-outline-color': [ - 'interpolate', ['linear'], ['zoom'], - 0, 'hsl(110, 0.7, 0.055)', - 10, 'hsla(330,0.85,50%)', - ], - }, - }], + layers: [ + { + id: '1', + type: 'fill', + source: 'vector', + paint: { + 'fill-color': 'hsl(100,0.3,.2)', + 'fill-outline-color': [ + 'interpolate', + ['linear'], + ['zoom'], + 0, + 'hsl(110, 0.7, 0.055)', + 10, + 'hsla(330,0.85,50%)' + ] + } + } + ] }); expect(migrated.layers[0].paint).toEqual({ 'fill-color': 'hsl(100,30%,20%)', 'fill-outline-color': [ - 'interpolate', ['linear'], ['zoom'], - 0, 'hsl(110,70%,5.5%)', - 10, 'hsl(330,85%,50%)', - ], + 'interpolate', + ['linear'], + ['zoom'], + 0, + 'hsl(110,70%,5.5%)', + 10, + 'hsl(330,85%,50%)' + ] }); }); @@ -145,21 +168,23 @@ describe('migrate', () => { const style: StyleSpecification = { version: 8, sources: {}, - layers: [{ - id: 'layer-with-transition', - type: 'symbol', - source: 'vector-source', - paint: { - 'icon-color': 'hsl(100,0.3,.2)', - 'icon-opacity-transition': {duration: 0}, - }, - }], + layers: [ + { + id: 'layer-with-transition', + type: 'symbol', + source: 'vector-source', + paint: { + 'icon-color': 'hsl(100,0.3,.2)', + 'icon-opacity-transition': {duration: 0} + } + } + ] }; expect(() => migrate(style)).not.toThrow(); expect(style.layers[0].paint).toEqual({ 'icon-color': 'hsl(100,30%,20%)', - 'icon-opacity-transition': {duration: 0}, + 'icon-opacity-transition': {duration: 0} }); }); }); diff --git a/src/migrate.ts b/src/migrate.ts index 7aefcfd2f..04f174f35 100644 --- a/src/migrate.ts +++ b/src/migrate.ts @@ -1,4 +1,3 @@ - import {migrateV8} from './migrate/v8'; import {expressions} from './migrate/expressions'; import {migrateColors} from './migrate/migrate_colors'; @@ -19,7 +18,7 @@ import type {StyleSpecification} from './types.g'; export function migrate(style: StyleSpecification): StyleSpecification { let migrated = false; - if (style.version as any === 7) { + if ((style.version as any) === 7) { style = migrateV8(style); migrated = true; } diff --git a/src/migrate/expressions.ts b/src/migrate/expressions.ts index 10402fda4..9a9359b48 100644 --- a/src/migrate/expressions.ts +++ b/src/migrate/expressions.ts @@ -15,7 +15,7 @@ import type {FilterSpecification, LayerSpecification, StyleSpecification} from ' export function expressions(style: StyleSpecification) { const converted = []; - eachLayer(style, (layer: LayerSpecification & { filter?: FilterSpecification }) => { + eachLayer(style, (layer: LayerSpecification & {filter?: FilterSpecification}) => { if (layer.filter) { layer.filter = convertFilter(layer.filter); } @@ -33,4 +33,3 @@ export function expressions(style: StyleSpecification) { return style; } - diff --git a/src/migrate/migrate_colors.test.ts b/src/migrate/migrate_colors.test.ts index 19b2875e7..4efb58478 100644 --- a/src/migrate/migrate_colors.test.ts +++ b/src/migrate/migrate_colors.test.ts @@ -2,7 +2,6 @@ import {migrateColors} from './migrate_colors'; import {describe, test, expect} from 'vitest'; describe('migrate colors', () => { - test('should convert hsl to a format compliant with CSS Color specification', () => { expect(migrateColors('hsla(0, 0, 0, 0)')).toBe('hsla(0,0%,0%,0)'); expect(migrateColors('hsl(900, 0.15, 90%)')).toBe('hsl(900,15%,90%)'); @@ -10,16 +9,25 @@ describe('migrate colors', () => { expect(migrateColors('hsl(900, 15%, 90%)')).toBe('hsl(900,15%,90%)'); expect(migrateColors('hsla(900, 15%, 90%)')).toBe('hsl(900,15%,90%)'); expect(migrateColors('hsla(900, 15%, 90%, 1)')).toBe('hsla(900,15%,90%,1)'); - expect(migrateColors([ - 'interpolate', ['linear'], ['zoom'], - 0, 'hsla(900,0.85,0.05,0)', - 10, 'hsla(900, .20, .0155, 1)', - ])).toEqual([ - 'interpolate', ['linear'], ['zoom'], - 0, 'hsla(900,85%,5%,0)', - 10, 'hsla(900,20%,1.55%,1)', + expect( + migrateColors([ + 'interpolate', + ['linear'], + ['zoom'], + 0, + 'hsla(900,0.85,0.05,0)', + 10, + 'hsla(900, .20, .0155, 1)' + ]) + ).toEqual([ + 'interpolate', + ['linear'], + ['zoom'], + 0, + 'hsla(900,85%,5%,0)', + 10, + 'hsla(900,20%,1.55%,1)' ]); expect(migrateColors('hsl(9001590)')).toBe('hsl(9001590)'); // invalid - no changes }); - }); diff --git a/src/migrate/migrate_colors.ts b/src/migrate/migrate_colors.ts index 64649d9cd..f66d997be 100644 --- a/src/migrate/migrate_colors.ts +++ b/src/migrate/migrate_colors.ts @@ -27,7 +27,7 @@ function migrateHslColors(colorToMigrate: string): string { const argsMatch = hslArgs.match(/^(.+?)\s*,\s*(.+?)\s*,\s*(.+?)(?:\s*,\s*(.+))?$/i); if (argsMatch) { let [h, s, l, a] = argsMatch.slice(1); - [s, l] = [s, l].map(v => v.endsWith('%') ? v : `${parseFloat(v) * 100}%`); + [s, l] = [s, l].map((v) => (v.endsWith('%') ? v : `${parseFloat(v) * 100}%`)); return `"hsl${typeof a === 'string' ? 'a' : ''}(${[h, s, l, a].filter(Boolean).join(',')})"`; } return match; diff --git a/src/migrate/v8.test.ts b/src/migrate/v8.test.ts index 1ca59d271..ee1a756c1 100644 --- a/src/migrate/v8.test.ts +++ b/src/migrate/v8.test.ts @@ -4,19 +4,20 @@ import {describe, test, expect} from 'vitest'; describe('migrate v8', () => { test('split text-font', () => { const input = { - 'version': 7, - 'sources': { - 'vector': { - 'type': 'vector', 'url': 'http://www.example.com/example.json' + version: 7, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': 'Helvetica, Arial', 'text-field': '{foo}' } @@ -25,19 +26,20 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': { - 'type': 'vector', 'url': 'http://www.example.com/example.json' + version: 8, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': ['Helvetica', 'Arial'], 'text-field': '{foo}' } @@ -50,19 +52,20 @@ describe('migrate v8', () => { test('rename symbol-min-distance', () => { const input = { - 'version': 7, - 'sources': { - 'vector': { - 'type': 'vector', 'url': 'http://www.example.com/example.json' + version: 7, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'symbol-min-distance': 2 } } @@ -70,19 +73,20 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': { - 'type': 'vector', 'url': 'http://www.example.com/example.json' + version: 8, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'symbol-spacing': 2 } } @@ -94,25 +98,37 @@ describe('migrate v8', () => { test('renames urls', () => { const input = { - 'version': 7, - 'sources': { - 'vector': { - 'type': 'video', 'url': ['foo'], - coordinates: [[1, 0], [1, 0], [1, 0], [1, 0]] + version: 7, + sources: { + vector: { + type: 'video', + url: ['foo'], + coordinates: [ + [1, 0], + [1, 0], + [1, 0], + [1, 0] + ] } }, - 'layers': [] + layers: [] } as any; const output = { - 'version': 8, - 'sources': { - 'vector': { - 'type': 'video', 'urls': ['foo'], - coordinates: [[0, 1], [0, 1], [0, 1], [0, 1]] + version: 8, + sources: { + vector: { + type: 'video', + urls: ['foo'], + coordinates: [ + [0, 1], + [0, 1], + [0, 1], + [0, 1] + ] } }, - 'layers': [] + layers: [] }; expect(migrate(input)).toEqual(output); @@ -120,47 +136,57 @@ describe('migrate v8', () => { test('not migrate interpolated functions', () => { const input = { - 'version': 7, - 'sources': { - 'vector': { - 'type': 'vector', - 'url': 'http://www.example.com/example.json' + version: 7, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [{ - 'id': 'functions', - 'type': 'symbol', - 'source': 'vector', - 'source-layer': 'layer', - 'layout': { - 'line-width': { - base: 2, - stops: [[1, 2], [3, 6]] + layers: [ + { + id: 'functions', + type: 'symbol', + source: 'vector', + 'source-layer': 'layer', + layout: { + 'line-width': { + base: 2, + stops: [ + [1, 2], + [3, 6] + ] + } } } - }] + ] } as any; const output = { - 'version': 8, - 'sources': { - 'vector': { - 'type': 'vector', - 'url': 'http://www.example.com/example.json' + version: 8, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [{ - 'id': 'functions', - 'type': 'symbol', - 'source': 'vector', - 'source-layer': 'layer', - 'layout': { - 'line-width': { - base: 2, - stops: [[1, 2], [3, 6]] + layers: [ + { + id: 'functions', + type: 'symbol', + source: 'vector', + 'source-layer': 'layer', + layout: { + 'line-width': { + base: 2, + stops: [ + [1, 2], + [3, 6] + ] + } } } - }] + ] }; expect(migrate(input)).toEqual(output); @@ -168,45 +194,55 @@ describe('migrate v8', () => { test('not migrate piecewise-constant functions', () => { const input = { - 'version': 7, - 'sources': { - 'vector': { - 'type': 'vector', - 'url': 'http://www.example.com/example.json' + version: 7, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [{ - 'id': 'functions', - 'type': 'symbol', - 'source': 'vector', - 'source-layer': 'layer', - 'layout': { - 'text-transform': { - stops: [[1, 'uppercase'], [3, 'lowercase']], + layers: [ + { + id: 'functions', + type: 'symbol', + source: 'vector', + 'source-layer': 'layer', + layout: { + 'text-transform': { + stops: [ + [1, 'uppercase'], + [3, 'lowercase'] + ] + } } } - }] + ] } as any; const output = { - 'version': 8, - 'sources': { - 'vector': { - 'type': 'vector', - 'url': 'http://www.example.com/example.json' + version: 8, + sources: { + vector: { + type: 'vector', + url: 'http://www.example.com/example.json' } }, - 'layers': [{ - 'id': 'functions', - 'type': 'symbol', - 'source': 'vector', - 'source-layer': 'layer', - 'layout': { - 'text-transform': { - stops: [[1, 'uppercase'], [3, 'lowercase']], + layers: [ + { + id: 'functions', + type: 'symbol', + source: 'vector', + 'source-layer': 'layer', + layout: { + 'text-transform': { + stops: [ + [1, 'uppercase'], + [3, 'lowercase'] + ] + } } } - }] + ] }; expect(migrate(input)).toEqual(output); @@ -214,20 +250,20 @@ describe('migrate v8', () => { test('inline constants', () => { const input = { - 'version': 7, - 'constants': { + version: 7, + constants: { '@foo': 0.5 }, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'fill', - 'source': 'vector', + id: 'minimum', + type: 'fill', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'fill-opacity': '@foo' } } @@ -235,17 +271,17 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + version: 8, + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'fill', - 'source': 'vector', + id: 'minimum', + type: 'fill', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'fill-opacity': 0.5 } } @@ -257,20 +293,20 @@ describe('migrate v8', () => { test('migrate and inline fontstack constants', () => { const input = { - 'version': 7, - 'constants': { + version: 7, + constants: { '@foo': 'Arial Unicode,Foo Bar' }, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': '@foo' } } @@ -278,17 +314,17 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + version: 8, + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': ['Arial Unicode', 'Foo Bar'] } } @@ -300,28 +336,22 @@ describe('migrate v8', () => { test('update fontstack function', () => { const input = { - 'version': 7, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + version: 7, + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': { - 'base': 1, - 'stops': [ - [ - 0, - 'Open Sans Regular, Arial Unicode MS Regular' - ], - [ - 6, - 'Open Sans Semibold, Arial Unicode MS Regular' - ] + base: 1, + stops: [ + [0, 'Open Sans Regular, Arial Unicode MS Regular'], + [6, 'Open Sans Semibold, Arial Unicode MS Regular'] ] } } @@ -330,20 +360,20 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + version: 8, + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': { - 'base': 1, - 'stops': [ + base: 1, + stops: [ [0, ['Open Sans Regular', 'Arial Unicode MS Regular']], [6, ['Open Sans Semibold', 'Arial Unicode MS Regular']] ] @@ -358,32 +388,26 @@ describe('migrate v8', () => { test('inline and migrate fontstack constant function', () => { const input = { - 'version': 7, - 'constants': { + version: 7, + constants: { '@function': { - 'base': 1, - 'stops': [ - [ - 0, - 'Open Sans Regular, Arial Unicode MS Regular' - ], - [ - 6, - 'Open Sans Semibold, Arial Unicode MS Regular' - ] + base: 1, + stops: [ + [0, 'Open Sans Regular, Arial Unicode MS Regular'], + [6, 'Open Sans Semibold, Arial Unicode MS Regular'] ] } }, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': '@function' } } @@ -391,20 +415,20 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + version: 8, + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': { - 'base': 1, - 'stops': [ + base: 1, + stops: [ [0, ['Open Sans Regular', 'Arial Unicode MS Regular']], [6, ['Open Sans Semibold', 'Arial Unicode MS Regular']] ] @@ -419,24 +443,24 @@ describe('migrate v8', () => { test('update fontstack function constant', () => { const input = { - 'version': 7, - 'constants': { + version: 7, + constants: { '@font-stack-a': 'Open Sans Regular, Arial Unicode MS Regular', '@font-stack-b': 'Open Sans Semibold, Arial Unicode MS Regular' }, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': { - 'base': 1, - 'stops': [ + base: 1, + stops: [ [0, '@font-stack-a'], [6, '@font-stack-b'] ] @@ -447,20 +471,20 @@ describe('migrate v8', () => { } as any; const output = { - 'version': 8, - 'sources': { - 'vector': {'type': 'vector', 'url': 'http://www.example.com/example.json'} + version: 8, + sources: { + vector: {type: 'vector', url: 'http://www.example.com/example.json'} }, - 'layers': [ + layers: [ { - 'id': 'minimum', - 'type': 'symbol', - 'source': 'vector', + id: 'minimum', + type: 'symbol', + source: 'vector', 'source-layer': 'layer', - 'layout': { + layout: { 'text-font': { - 'base': 1, - 'stops': [ + base: 1, + stops: [ [0, ['Open Sans Regular', 'Arial Unicode MS Regular']], [6, ['Open Sans Semibold', 'Arial Unicode MS Regular']] ] @@ -472,5 +496,4 @@ describe('migrate v8', () => { expect(migrate(input)).toEqual(output); }); - }); diff --git a/src/migrate/v8.ts b/src/migrate/v8.ts index b763d6d3a..25795a678 100644 --- a/src/migrate/v8.ts +++ b/src/migrate/v8.ts @@ -1,8 +1,10 @@ - import {eachSource, eachLayer, eachProperty} from '../visit'; import type {LayerSpecification, StyleSpecification} from '../types.g'; -function eachLayout(layer: LayerSpecification, callback: (_: LayerSpecification['layout'], __: string) => void) { +function eachLayout( + layer: LayerSpecification, + callback: (_: LayerSpecification['layout'], __: string) => void +) { for (const k in layer) { if (k.indexOf('layout') === 0) { callback(layer[k], k); @@ -10,7 +12,10 @@ function eachLayout(layer: LayerSpecification, callback: (_: LayerSpecification[ } } -function eachPaint(layer: LayerSpecification, callback: (_: LayerSpecification['paint'], __: string) => void) { +function eachPaint( + layer: LayerSpecification, + callback: (_: LayerSpecification['paint'], __: string) => void +) { for (const k in layer) { if (k.indexOf('paint') === 0) { callback(layer[k], k); @@ -31,7 +36,8 @@ function isFunction(value) { } function renameProperty(obj: Object, from: string, to: string) { - obj[to] = obj[from]; delete obj[from]; + obj[to] = obj[from]; + delete obj[from]; } export function migrateV8(style: StyleSpecification) { @@ -118,16 +124,13 @@ export function migrateV8(style: StyleSpecification) { if (Array.isArray(font)) { // Assume it's a previously migrated font-array. return font; - } else if (typeof font === 'string') { return splitAndTrim(font); - } else if (typeof font === 'object') { font.stops.forEach((stop) => { stop[1] = splitAndTrim(stop[1]); }); return font; - } else { throw new Error('unexpected font value'); } diff --git a/src/point2d.ts b/src/point2d.ts index fb6fd1504..9f4a83844 100644 --- a/src/point2d.ts +++ b/src/point2d.ts @@ -1,4 +1,3 @@ - export interface Point2D { x: number; y: number; diff --git a/src/reference/latest.ts b/src/reference/latest.ts index 3de2d2033..434833d2f 100644 --- a/src/reference/latest.ts +++ b/src/reference/latest.ts @@ -1,4 +1,3 @@ - -import latest from './v8.json' with { type: 'json' }; -export {latest} -export default latest as any +import latest from './v8.json' with {type: 'json'}; +export {latest}; +export default latest as any; diff --git a/src/tiles_and_coordinates.ts b/src/tiles_and_coordinates.ts index 66780c3f7..f5a4028ef 100644 --- a/src/tiles_and_coordinates.ts +++ b/src/tiles_and_coordinates.ts @@ -32,10 +32,14 @@ export interface ILngLat { toString(): string; } -export type ILngLatLike = ILngLat | { - lng: number; - lat: number; -} | { - lon: number; - lat: number; -} | [number, number]; +export type ILngLatLike = + | ILngLat + | { + lng: number; + lat: number; + } + | { + lon: number; + lat: number; + } + | [number, number]; diff --git a/src/util/cheap_ruler.ts b/src/util/cheap_ruler.ts index 3a27add76..2a0a2ac88 100644 --- a/src/util/cheap_ruler.ts +++ b/src/util/cheap_ruler.ts @@ -12,7 +12,6 @@ export class CheapRuler { private ky: number; constructor(lat: number) { - // Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional const m = RAD * RE * 1000; const coslat = Math.cos(lat * RAD); @@ -20,7 +19,7 @@ export class CheapRuler { const w = Math.sqrt(w2); // multipliers for converting longitude and latitude degrees into distance - this.kx = m * w * coslat; // based on normal radius of curvature + this.kx = m * w * coslat; // based on normal radius of curvature this.ky = m * w * w2 * (1 - E2); // based on meridional radius of curvature } @@ -57,7 +56,6 @@ export class CheapRuler { let minX: number, minY: number, minI: number, minT: number; for (let i = 0; i < line.length - 1; i++) { - let x = line[i][0]; let y = line[i][1]; let dx = this.wrap(line[i + 1][0] - x) * this.kx; @@ -65,12 +63,13 @@ export class CheapRuler { let t = 0; if (dx !== 0 || dy !== 0) { - t = (this.wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy); + t = + (this.wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / + (dx * dx + dy * dy); if (t > 1) { x = line[i + 1][0]; y = line[i + 1][1]; - } else if (t > 0) { x += (dx / this.kx) * t; y += (dy / this.ky) * t; diff --git a/src/util/classify_rings.test.ts b/src/util/classify_rings.test.ts index dd0c08dbb..dad29f763 100644 --- a/src/util/classify_rings.test.ts +++ b/src/util/classify_rings.test.ts @@ -63,15 +63,30 @@ describe('classifyRings', () => { }); describe('classifyRings + maxRings', () => { - function createGeometry(options?) { const geometry = [ // Outer ring, area = 3200 - [{x: 0, y: 0}, {x: 0, y: 40}, {x: 40, y: 40}, {x: 40, y: 0}, {x: 0, y: 0}], + [ + {x: 0, y: 0}, + {x: 0, y: 40}, + {x: 40, y: 40}, + {x: 40, y: 0}, + {x: 0, y: 0} + ], // Inner ring, area = 100 - [{x: 30, y: 30}, {x: 32, y: 30}, {x: 32, y: 32}, {x: 30, y: 30}], + [ + {x: 30, y: 30}, + {x: 32, y: 30}, + {x: 32, y: 32}, + {x: 30, y: 30} + ], // Inner ring, area = 4 - [{x: 10, y: 10}, {x: 20, y: 10}, {x: 20, y: 20}, {x: 10, y: 10}] + [ + {x: 10, y: 10}, + {x: 20, y: 10}, + {x: 20, y: 20}, + {x: 10, y: 10} + ] ] as Point2D[][]; if (options && options.reverse) { geometry[0].reverse(); @@ -96,7 +111,6 @@ describe('classifyRings + maxRings', () => { expect(geometry[0]).toHaveLength(2); expect(geometry[0][0].area).toBe(3200); expect(geometry[0][1].area).toBe(100); - }); test('maxRings=2, reversed geometry', () => { diff --git a/src/util/classify_rings.ts b/src/util/classify_rings.ts index 6692f637d..f5c640aa0 100644 --- a/src/util/classify_rings.ts +++ b/src/util/classify_rings.ts @@ -1,7 +1,7 @@ import quickselect from 'quickselect'; import {Point2D} from '../point2d'; -export type RingWithArea = T[] & { area?: number }; +export type RingWithArea = T[] & {area?: number}; /** * Classifies an array of rings into polygons with outer rings and holes @@ -9,7 +9,10 @@ export type RingWithArea = T[] & { area?: number }; * @param maxRings - the maximum number of rings to include in a polygon, use 0 to include all rings * @returns an array of polygons with internal rings as holes */ -export function classifyRings(rings: RingWithArea[], maxRings?: number): RingWithArea[][] { +export function classifyRings( + rings: RingWithArea[], + maxRings?: number +): RingWithArea[][] { const len = rings.length; if (len <= 1) return [rings]; diff --git a/src/util/deep_equal.ts b/src/util/deep_equal.ts index 765c7fd8c..70295806f 100644 --- a/src/util/deep_equal.ts +++ b/src/util/deep_equal.ts @@ -22,4 +22,4 @@ export function deepEqual(a?: unknown | null, b?: unknown | null): boolean { return true; } return a === b; -} \ No newline at end of file +} diff --git a/src/util/geometry_util.ts b/src/util/geometry_util.ts index 5fce15d37..5c687c8a7 100644 --- a/src/util/geometry_util.ts +++ b/src/util/geometry_util.ts @@ -5,14 +5,20 @@ export type BBox = [number, number, number, number]; export const EXTENT = 8192; -export function getTileCoordinates(p: GeoJSON.Position, canonical: ICanonicalTileID): [number, number] { +export function getTileCoordinates( + p: GeoJSON.Position, + canonical: ICanonicalTileID +): [number, number] { const x = mercatorXfromLng(p[0]); const y = mercatorYfromLat(p[1]); const tilesAtZoom = Math.pow(2, canonical.z); return [Math.round(x * tilesAtZoom * EXTENT), Math.round(y * tilesAtZoom * EXTENT)]; } -export function getLngLatFromTileCoord(coord: [number, number], canonical: ICanonicalTileID): GeoJSON.Position { +export function getLngLatFromTileCoord( + coord: [number, number], + canonical: ICanonicalTileID +): GeoJSON.Position { const tilesAtZoom = Math.pow(2, canonical.z); const x = (coord[0] / EXTENT + canonical.x) / tilesAtZoom; const y = (coord[1] / EXTENT + canonical.y) / tilesAtZoom; @@ -28,11 +34,11 @@ function lngFromMercatorXfromLng(mercatorX: number) { } function mercatorYfromLat(lat: number) { - return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360; + return (180 - (180 / Math.PI) * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360))) / 360; } function latFromMercatorY(mercatorY: number) { - return 360 / Math.PI * Math.atan(Math.exp((180 - mercatorY * 360) * Math.PI / 180)) - 90; + return (360 / Math.PI) * Math.atan(Math.exp(((180 - mercatorY * 360) * Math.PI) / 180)) - 90; } export function updateBBox(bbox: BBox, coord: GeoJSON.Position) { @@ -50,8 +56,15 @@ export function boxWithinBox(bbox1: BBox, bbox2: BBox) { return true; } -export function rayIntersect(p: [number, number], p1: [number, number], p2: [number, number]): boolean { - return ((p1[1] > p[1]) !== (p2[1] > p[1])) && (p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]); +export function rayIntersect( + p: [number, number], + p1: [number, number], + p2: [number, number] +): boolean { + return ( + p1[1] > p[1] !== p2[1] > p[1] && + p[0] < ((p2[0] - p1[0]) * (p[1] - p1[1])) / (p2[1] - p1[1]) + p1[0] + ); } function pointOnBoundary(p: [number, number], p1: [number, number], p2: [number, number]): boolean { @@ -59,11 +72,16 @@ function pointOnBoundary(p: [number, number], p1: [number, number], p2: [number, const y1 = p[1] - p1[1]; const x2 = p[0] - p2[0]; const y2 = p[1] - p2[1]; - return (x1 * y2 - x2 * y1 === 0) && (x1 * x2 <= 0) && (y1 * y2 <= 0); + return x1 * y2 - x2 * y1 === 0 && x1 * x2 <= 0 && y1 * y2 <= 0; } // a, b are end points for line segment1, c and d are end points for line segment2 -export function segmentIntersectSegment(a: [number, number], b: [number, number], c: [number, number], d: [number, number]) { +export function segmentIntersectSegment( + a: [number, number], + b: [number, number], + c: [number, number], + d: [number, number] +) { // check if two segments are parallel or not // precondition is end point a, b is inside polygon, if line a->b is // parallel to polygon edge c->d, then a->b won't intersect with c->d @@ -91,7 +109,11 @@ export function lineIntersectPolygon(p1, p2, polygon) { } // ray casting algorithm for detecting if point is in polygon -export function pointWithinPolygon(point: [number, number], rings: [number, number][][], trueIfOnBoundary = false) { +export function pointWithinPolygon( + point: [number, number], + rings: [number, number][][], + trueIfOnBoundary = false +) { let inside = false; for (const ring of rings) { for (let j = 0; j < ring.length - 1; j++) { @@ -126,7 +148,10 @@ export function lineStringWithinPolygon(line: [number, number][], polygon: [numb return true; } -export function lineStringWithinPolygons(line: [number, number][], polygons: [number, number][][][]) { +export function lineStringWithinPolygons( + line: [number, number][], + polygons: [number, number][][][] +) { for (const polygon of polygons) { if (lineStringWithinPolygon(line, polygon)) return true; } @@ -134,11 +159,16 @@ export function lineStringWithinPolygons(line: [number, number][], polygons: [nu } function perp(v1: [number, number], v2: [number, number]) { - return (v1[0] * v2[1] - v1[1] * v2[0]); + return v1[0] * v2[1] - v1[1] * v2[0]; } // check if p1 and p2 are in different sides of line segment q1->q2 -function twoSided(p1: [number, number], p2: [number, number], q1: [number, number], q2: [number, number]) { +function twoSided( + p1: [number, number], + p2: [number, number], + q1: [number, number], + q2: [number, number] +) { // q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3) const x1 = p1[0] - q1[0]; const y1 = p1[1] - q1[1]; @@ -146,8 +176,8 @@ function twoSided(p1: [number, number], p2: [number, number], q1: [number, numb const y2 = p2[1] - q1[1]; const x3 = q2[0] - q1[0]; const y3 = q2[1] - q1[1]; - const det1 = (x1 * y3 - x3 * y1); - const det2 = (x2 * y3 - x3 * y2); + const det1 = x1 * y3 - x3 * y1; + const det2 = x2 * y3 - x3 * y2; if ((det1 > 0 && det2 < 0) || (det1 < 0 && det2 > 0)) return true; return false; } diff --git a/src/util/get_own.test.ts b/src/util/get_own.test.ts index 860da88b3..727f462c5 100644 --- a/src/util/get_own.test.ts +++ b/src/util/get_own.test.ts @@ -9,7 +9,7 @@ describe('get_own', () => { // hasOwn depends on whether Object.hasOwn is available vi.resetModules(); expect(Object.hasOwn).toBeInstanceOf(Function); - }, + } ], [ 'when Object.hasOwn is not available', @@ -17,8 +17,8 @@ describe('get_own', () => { vi.resetModules(); delete Object.hasOwn; expect(Object.hasOwn).toBeUndefined(); - }, - ], + } + ] ])('unit tests %s', (_, beforeAllFn) => { let getOwn: typeof import('./get_own').getOwn; diff --git a/src/util/get_own.ts b/src/util/get_own.ts index bfb1b1c3d..9e0a9a85c 100644 --- a/src/util/get_own.ts +++ b/src/util/get_own.ts @@ -1,4 +1,7 @@ -type HasOwnPropertyFn = (obj: TObject, key: PropertyKey) => key is keyof TObject; +type HasOwnPropertyFn = ( + obj: TObject, + key: PropertyKey +) => key is keyof TObject; // polyfill for Object.hasOwn const hasOwnProperty: HasOwnPropertyFn = diff --git a/src/util/interpolate-primitives.test.ts b/src/util/interpolate-primitives.test.ts index 8f39be955..53b95af73 100644 --- a/src/util/interpolate-primitives.test.ts +++ b/src/util/interpolate-primitives.test.ts @@ -2,13 +2,12 @@ import {interpolateArray, interpolateNumber} from './interpolate-primitives'; import {describe, test, expect} from 'vitest'; describe('interpolate', () => { - test('interpolate number', () => { - expect(interpolateNumber(-5, 5, 0.00)).toBe(-5.0); + expect(interpolateNumber(-5, 5, 0.0)).toBe(-5.0); expect(interpolateNumber(-5, 5, 0.25)).toBe(-2.5); - expect(interpolateNumber(-5, 5, 0.50)).toBe(0); + expect(interpolateNumber(-5, 5, 0.5)).toBe(0); expect(interpolateNumber(-5, 5, 0.75)).toBe(2.5); - expect(interpolateNumber(-5, 5, 1.00)).toBe(5.0); + expect(interpolateNumber(-5, 5, 1.0)).toBe(5.0); expect(interpolateNumber(0, 1, 0.5)).toBe(0.5); expect(interpolateNumber(-10, -5, 0.5)).toBe(-7.5); diff --git a/src/util/is_object_literal.ts b/src/util/is_object_literal.ts index 0778ab401..f3ff55bf3 100644 --- a/src/util/is_object_literal.ts +++ b/src/util/is_object_literal.ts @@ -1,5 +1,3 @@ -export function isObjectLiteral( - anything: unknown -): anything is Record { +export function isObjectLiteral(anything: unknown): anything is Record { return Boolean(anything) && anything.constructor === Object; -} \ No newline at end of file +} diff --git a/src/util/properties.ts b/src/util/properties.ts index ca434baba..09dd30e5d 100644 --- a/src/util/properties.ts +++ b/src/util/properties.ts @@ -1,7 +1,10 @@ import type {StylePropertySpecification} from '..'; export function supportsPropertyExpression(spec: StylePropertySpecification): boolean { - return spec['property-type'] === 'data-driven' || spec['property-type'] === 'cross-faded-data-driven'; + return ( + spec['property-type'] === 'data-driven' || + spec['property-type'] === 'cross-faded-data-driven' + ); } export function supportsZoomExpression(spec: StylePropertySpecification): boolean { diff --git a/src/util/ref_properties.ts b/src/util/ref_properties.ts index ce2cc3518..744daa329 100644 --- a/src/util/ref_properties.ts +++ b/src/util/ref_properties.ts @@ -1,2 +1,9 @@ - -export const refProperties = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout']; +export const refProperties = [ + 'type', + 'source', + 'source-layer', + 'minzoom', + 'maxzoom', + 'filter', + 'layout' +]; diff --git a/src/util/result.ts b/src/util/result.ts index 9fd30ed1e..0da651922 100644 --- a/src/util/result.ts +++ b/src/util/result.ts @@ -4,13 +4,15 @@ * contains an error value. * @private */ -export type Result = { - result: 'success'; - value: T; -} | { - result: 'error'; - value: E; -}; +export type Result = + | { + result: 'success'; + value: T; + } + | { + result: 'error'; + value: E; + }; export function success(value: T): Result { return {result: 'success', value}; diff --git a/src/util/unbundle_jsonlint.ts b/src/util/unbundle_jsonlint.ts index 43128dc9c..af270ab0b 100644 --- a/src/util/unbundle_jsonlint.ts +++ b/src/util/unbundle_jsonlint.ts @@ -10,7 +10,10 @@ export function unbundle(value: unknown) { export function deepUnbundle(value: unknown): unknown { if (Array.isArray(value)) { return value.map(deepUnbundle); - } else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) { + } else if ( + value instanceof Object && + !(value instanceof Number || value instanceof String || value instanceof Boolean) + ) { const unbundledValue: {[key: string]: unknown} = {}; for (const key in value) { unbundledValue[key] = deepUnbundle(value[key]); diff --git a/src/validate/validate.ts b/src/validate/validate.ts index 01251163c..63f85f28c 100644 --- a/src/validate/validate.ts +++ b/src/validate/validate.ts @@ -1,4 +1,3 @@ - import {extendBy} from '../util/extend'; import {unbundle, deepUnbundle} from '../util/unbundle_jsonlint'; import {isExpression} from '../expression'; @@ -37,32 +36,32 @@ const VALIDATORS = { '*'() { return []; }, - 'array': validateArray, - 'boolean': validateBoolean, - 'number': validateNumber, - 'color': validateColor, - 'constants': validateConstants, - 'enum': validateEnum, - 'filter': validateFilter, - 'function': validateFunction, - 'layer': validateLayer, - 'object': validateObject, - 'source': validateSource, - 'light': validateLight, - 'sky': validateSky, - 'terrain': validateTerrain, - 'projection': validateProjection, - 'projectionDefinition': validateProjectionDefinition, - 'string': validateString, - 'formatted': validateFormatted, - 'resolvedImage': validateImage, - 'padding': validatePadding, - 'numberArray': validateNumberArray, - 'colorArray': validateColorArray, - 'variableAnchorOffsetCollection': validateVariableAnchorOffsetCollection, - 'sprite': validateSprite, - 'state': validateState, - 'fontFaces': validateFontFaces + array: validateArray, + boolean: validateBoolean, + number: validateNumber, + color: validateColor, + constants: validateConstants, + enum: validateEnum, + filter: validateFilter, + function: validateFunction, + layer: validateLayer, + object: validateObject, + source: validateSource, + light: validateLight, + sky: validateSky, + terrain: validateTerrain, + projection: validateProjection, + projectionDefinition: validateProjectionDefinition, + string: validateString, + formatted: validateFormatted, + resolvedImage: validateImage, + padding: validatePadding, + numberArray: validateNumberArray, + colorArray: validateColorArray, + variableAnchorOffsetCollection: validateVariableAnchorOffsetCollection, + sprite: validateSprite, + state: validateState, + fontFaces: validateFontFaces }; /** @@ -88,7 +87,8 @@ export function validate(options: { styleSpec: any; validateSpec?: any; style: any; - objectElementValidators?: any;}): ValidationError[] { + objectElementValidators?: any; +}): ValidationError[] { const value = options.value; const valueSpec = options.valueSpec; const styleSpec = options.styleSpec; @@ -96,17 +96,16 @@ export function validate(options: { if (valueSpec.expression && isFunction(unbundle(value))) { return validateFunction(options); - } else if (valueSpec.expression && isExpression(deepUnbundle(value))) { return validateExpression(options); - } else if (valueSpec.type && VALIDATORS[valueSpec.type]) { return VALIDATORS[valueSpec.type](options); - } else { - const valid = validateObject(extendBy({}, options, { - valueSpec: valueSpec.type ? styleSpec[valueSpec.type] : valueSpec - })); + const valid = validateObject( + extendBy({}, options, { + valueSpec: valueSpec.type ? styleSpec[valueSpec.type] : valueSpec + }) + ); return valid; } } diff --git a/src/validate/validate_array.ts b/src/validate/validate_array.ts index 2d9038b12..fbc081aac 100644 --- a/src/validate/validate_array.ts +++ b/src/validate/validate_array.ts @@ -15,12 +15,18 @@ export function validateArray(options) { } if (arraySpec.length && array.length !== arraySpec.length) { - return [new ValidationError(key, array, `array length ${arraySpec.length} expected, length ${array.length} found`)]; + return [ + new ValidationError( + key, + array, + `array length ${arraySpec.length} expected, length ${array.length} found` + ) + ]; } let arrayElementSpec = { - 'type': arraySpec.value, - 'values': arraySpec.values + type: arraySpec.value, + values: arraySpec.values }; if (styleSpec.$version < 7) { @@ -33,16 +39,18 @@ export function validateArray(options) { let errors = []; for (let i = 0; i < array.length; i++) { - errors = errors.concat(validateArrayElement({ - array, - arrayIndex: i, - value: array[i], - valueSpec: arrayElementSpec, - validateSpec: options.validateSpec, - style, - styleSpec, - key: `${key}[${i}]` - })); + errors = errors.concat( + validateArrayElement({ + array, + arrayIndex: i, + value: array[i], + valueSpec: arrayElementSpec, + validateSpec: options.validateSpec, + style, + styleSpec, + key: `${key}[${i}]` + }) + ); } return errors; } diff --git a/src/validate/validate_boolean.ts b/src/validate/validate_boolean.ts index 17916afd4..da21d87f1 100644 --- a/src/validate/validate_boolean.ts +++ b/src/validate/validate_boolean.ts @@ -1,4 +1,3 @@ - import {getType} from '../util/get_type'; import {ValidationError} from '../error/validation_error'; diff --git a/src/validate/validate_color.test.ts b/src/validate/validate_color.test.ts index 26e5c481e..cd484b4de 100644 --- a/src/validate/validate_color.test.ts +++ b/src/validate/validate_color.test.ts @@ -2,7 +2,6 @@ import {validateColor} from './validate_color'; import {describe, test, expect} from 'vitest'; describe('validateColor function', () => { - const key = 'sample_color_key'; test('should return no errors when color is valid color string', () => { @@ -16,7 +15,7 @@ describe('validateColor function', () => { 'rgba(28,148,103,0.5)', 'rgb(28 148 103 / 50%)', 'hsl(0 0% 0%)', - 'hsla(158,68.2%,34.5%,0.5)', + 'hsla(158,68.2%,34.5%,0.5)' ]; for (const value of validColorStrings) { @@ -26,22 +25,22 @@ describe('validateColor function', () => { test('should return error when color is not a string', () => { expect(validateColor({key, value: 0})).toMatchObject([ - {message: `${key}: color expected, number found`}, + {message: `${key}: color expected, number found`} ]); expect(validateColor({key, value: [0, 0, 0]})).toMatchObject([ - {message: `${key}: color expected, array found`}, + {message: `${key}: color expected, array found`} ]); expect(validateColor({key, value: {}})).toMatchObject([ - {message: `${key}: color expected, object found`}, + {message: `${key}: color expected, object found`} ]); expect(validateColor({key, value: false})).toMatchObject([ - {message: `${key}: color expected, boolean found`}, + {message: `${key}: color expected, boolean found`} ]); expect(validateColor({key, value: null})).toMatchObject([ - {message: `${key}: color expected, null found`}, + {message: `${key}: color expected, null found`} ]); expect(validateColor({key, value: undefined})).toMatchObject([ - {message: `${key}: color expected, undefined found`}, + {message: `${key}: color expected, undefined found`} ]); }); @@ -53,14 +52,13 @@ describe('validateColor function', () => { 'rgb(28 148 / 0.8)', 'rgb(28 148 103 0.5)', 'rgb(28 148 103 / 0.5 2)', - 'hsl(0,0,0)', + 'hsl(0,0,0)' ]; for (const value of invalidColorStrings) { expect(validateColor({key, value})).toEqual([ - {message: `${key}: color expected, "${value}" found`}, + {message: `${key}: color expected, "${value}" found`} ]); } }); - }); diff --git a/src/validate/validate_color.ts b/src/validate/validate_color.ts index 4279f5e03..539a9cfc7 100644 --- a/src/validate/validate_color.ts +++ b/src/validate/validate_color.ts @@ -11,7 +11,8 @@ export function validateColor(options) { return [new ValidationError(key, value, `color expected, ${type} found`)]; } - if (!Color.parse(String(value))) { // cast String object to string primitive + if (!Color.parse(String(value))) { + // cast String object to string primitive return [new ValidationError(key, value, `color expected, "${value}" found`)]; } diff --git a/src/validate/validate_color_array.test.ts b/src/validate/validate_color_array.test.ts index 3f54a5fcb..e98595b04 100644 --- a/src/validate/validate_color_array.test.ts +++ b/src/validate/validate_color_array.test.ts @@ -16,7 +16,11 @@ describe('Validate ColorArray', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('colorArray: color expected, null found'); - errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: {x: 1, y: 1}}); + errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: {x: 1, y: 1} + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('colorArray: color expected, object found'); @@ -30,24 +34,40 @@ describe('Validate ColorArray', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('colorArray: color expected, "3" found'); - errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: ['3', 'words']}); + errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: ['3', 'words'] + }); expect(errors).toHaveLength(2); expect(errors[0].message).toBe('colorArray[0]: color expected, "3" found'); expect(errors[1].message).toBe('colorArray[1]: color expected, "words" found'); - errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: ['#012', 'words']}); + errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: ['#012', 'words'] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('colorArray[1]: color expected, "words" found'); }); test('Should pass if type is color', () => { - let errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: '#987654'}); + let errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: '#987654' + }); expect(errors).toHaveLength(0); errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: 'red'}); expect(errors).toHaveLength(0); - errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: '#987654ff'}); + errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: '#987654ff' + }); expect(errors).toHaveLength(0); errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: '#987'}); @@ -57,15 +77,25 @@ describe('Validate ColorArray', () => { test('Should pass if type is array of colors', () => { let errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: []}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('colorArray: array length at least 1 expected, length 0 found'); + expect(errors[0].message).toBe( + 'colorArray: array length at least 1 expected, length 0 found' + ); errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: ['red']}); expect(errors).toHaveLength(0); - errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: ['red', 'blue']}); + errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: ['red', 'blue'] + }); expect(errors).toHaveLength(0); - errors = validateColorArray({validateSpec: validate, key: 'colorArray', value: ['red', 'blue', '#012', '#12345678', '#012345']}); + errors = validateColorArray({ + validateSpec: validate, + key: 'colorArray', + value: ['red', 'blue', '#012', '#12345678', '#012345'] + }); expect(errors).toHaveLength(0); }); }); diff --git a/src/validate/validate_color_array.ts b/src/validate/validate_color_array.ts index ddd506be2..14deea62e 100644 --- a/src/validate/validate_color_array.ts +++ b/src/validate/validate_color_array.ts @@ -8,18 +8,21 @@ export function validateColorArray(options) { const type = getType(value); if (type === 'array') { - if (value.length < 1) { - return [new ValidationError(key, value, 'array length at least 1 expected, length 0 found')]; + return [ + new ValidationError(key, value, 'array length at least 1 expected, length 0 found') + ]; } let errors = []; for (let i = 0; i < value.length; i++) { - errors = errors.concat(validateColor({ - key: `${key}[${i}]`, - value: value[i], - valueSpec: {} - })); + errors = errors.concat( + validateColor({ + key: `${key}[${i}]`, + value: value[i], + valueSpec: {} + }) + ); } return errors; } else { diff --git a/src/validate/validate_constants.ts b/src/validate/validate_constants.ts index 9e27f4bf4..30d9ddc8a 100644 --- a/src/validate/validate_constants.ts +++ b/src/validate/validate_constants.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; export function validateConstants(options) { diff --git a/src/validate/validate_enum.ts b/src/validate/validate_enum.ts index 843556fb4..902643177 100644 --- a/src/validate/validate_enum.ts +++ b/src/validate/validate_enum.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {unbundle} from '../util/unbundle_jsonlint'; @@ -8,13 +7,27 @@ export function validateEnum(options) { const valueSpec = options.valueSpec; const errors = []; - if (Array.isArray(valueSpec.values)) { // <=v7 + if (Array.isArray(valueSpec.values)) { + // <=v7 if (valueSpec.values.indexOf(unbundle(value)) === -1) { - errors.push(new ValidationError(key, value, `expected one of [${valueSpec.values.join(', ')}], ${JSON.stringify(value)} found`)); + errors.push( + new ValidationError( + key, + value, + `expected one of [${valueSpec.values.join(', ')}], ${JSON.stringify(value)} found` + ) + ); } - } else { // >=v8 + } else { + // >=v8 if (Object.keys(valueSpec.values).indexOf(unbundle(value) as string) === -1) { - errors.push(new ValidationError(key, value, `expected one of [${Object.keys(valueSpec.values).join(', ')}], ${JSON.stringify(value)} found`)); + errors.push( + new ValidationError( + key, + value, + `expected one of [${Object.keys(valueSpec.values).join(', ')}], ${JSON.stringify(value)} found` + ) + ); } } return errors; diff --git a/src/validate/validate_expression.ts b/src/validate/validate_expression.ts index 652afc05a..6bf028520 100644 --- a/src/validate/validate_expression.ts +++ b/src/validate/validate_expression.ts @@ -3,42 +3,84 @@ import {ValidationError} from '../error/validation_error'; import {createExpression, createPropertyExpression} from '../expression'; import {deepUnbundle} from '../util/unbundle_jsonlint'; -import {isFeatureConstant, +import { + isFeatureConstant, isGlobalPropertyConstant, - isStateConstant} from '../expression/compound_expression'; + isStateConstant +} from '../expression/compound_expression'; import {Expression} from '../expression/expression'; export function validateExpression(options: any): Array { - const expression = (options.expressionContext === 'property' ? createPropertyExpression : createExpression)(deepUnbundle(options.value), options.valueSpec); + const expression = ( + options.expressionContext === 'property' ? createPropertyExpression : createExpression + )(deepUnbundle(options.value), options.valueSpec); if (expression.result === 'error') { return expression.value.map((error) => { return new ValidationError(`${options.key}${error.key}`, options.value, error.message); }); } - const expressionObj: Expression = (expression.value as any).expression || (expression.value as any)._styleExpression.expression; + const expressionObj: Expression = + (expression.value as any).expression || + (expression.value as any)._styleExpression.expression; - if (options.expressionContext === 'property' && (options.propertyKey === 'text-font') && - !expressionObj.outputDefined()) { - return [new ValidationError(options.key, options.value, `Invalid data expression for "${options.propertyKey}". Output values must be contained as literals within the expression.`)]; + if ( + options.expressionContext === 'property' && + options.propertyKey === 'text-font' && + !expressionObj.outputDefined() + ) { + return [ + new ValidationError( + options.key, + options.value, + `Invalid data expression for "${options.propertyKey}". Output values must be contained as literals within the expression.` + ) + ]; } - if (options.expressionContext === 'property' && options.propertyType === 'layout' && - (!isStateConstant(expressionObj))) { - return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with layout properties.')]; + if ( + options.expressionContext === 'property' && + options.propertyType === 'layout' && + !isStateConstant(expressionObj) + ) { + return [ + new ValidationError( + options.key, + options.value, + '"feature-state" data expressions are not supported with layout properties.' + ) + ]; } if (options.expressionContext === 'filter' && !isStateConstant(expressionObj)) { - return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with filters.')]; + return [ + new ValidationError( + options.key, + options.value, + '"feature-state" data expressions are not supported with filters.' + ) + ]; } if (options.expressionContext && options.expressionContext.indexOf('cluster') === 0) { if (!isGlobalPropertyConstant(expressionObj, ['zoom', 'feature-state'])) { - return [new ValidationError(options.key, options.value, '"zoom" and "feature-state" expressions are not supported with cluster properties.')]; + return [ + new ValidationError( + options.key, + options.value, + '"zoom" and "feature-state" expressions are not supported with cluster properties.' + ) + ]; } if (options.expressionContext === 'cluster-initial' && !isFeatureConstant(expressionObj)) { - return [new ValidationError(options.key, options.value, 'Feature data expressions are not supported with initial expression part of cluster properties.')]; + return [ + new ValidationError( + options.key, + options.value, + 'Feature data expressions are not supported with initial expression part of cluster properties.' + ) + ]; } } diff --git a/src/validate/validate_filter.ts b/src/validate/validate_filter.ts index 544841291..a50aa311e 100644 --- a/src/validate/validate_filter.ts +++ b/src/validate/validate_filter.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {validateExpression} from './validate_expression'; import {validateEnum} from './validate_enum'; @@ -9,10 +8,12 @@ import {isExpressionFilter} from '../feature_filter'; export function validateFilter(options) { if (isExpressionFilter(deepUnbundle(options.value))) { - return validateExpression(extend({}, options, { - expressionContext: 'filter', - valueSpec: {value: 'boolean'} - })); + return validateExpression( + extend({}, options, { + expressionContext: 'filter', + valueSpec: {value: 'boolean'} + }) + ); } else { return validateNonExpressionFilter(options); } @@ -35,13 +36,15 @@ function validateNonExpressionFilter(options) { return [new ValidationError(key, value, 'filter array must have at least 1 element')]; } - errors = errors.concat(validateEnum({ - key: `${key}[0]`, - value: value[0], - valueSpec: styleSpec.filter_operator, - style: options.style, - styleSpec: options.styleSpec - })); + errors = errors.concat( + validateEnum({ + key: `${key}[0]`, + value: value[0], + valueSpec: styleSpec.filter_operator, + style: options.style, + styleSpec: options.styleSpec + }) + ); switch (unbundle(value[0])) { case '<': @@ -49,13 +52,25 @@ function validateNonExpressionFilter(options) { case '>': case '>=': if (value.length >= 2 && unbundle(value[1]) === '$type') { - errors.push(new ValidationError(key, value, `"$type" cannot be use with operator "${value[0]}"`)); + errors.push( + new ValidationError( + key, + value, + `"$type" cannot be use with operator "${value[0]}"` + ) + ); } /* falls through */ case '==': case '!=': if (value.length !== 3) { - errors.push(new ValidationError(key, value, `filter array for operator "${value[0]}" must have 3 elements`)); + errors.push( + new ValidationError( + key, + value, + `filter array for operator "${value[0]}" must have 3 elements` + ) + ); } /* falls through */ case 'in': @@ -63,21 +78,31 @@ function validateNonExpressionFilter(options) { if (value.length >= 2) { type = getType(value[1]); if (type !== 'string') { - errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`)); + errors.push( + new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`) + ); } } for (let i = 2; i < value.length; i++) { type = getType(value[i]); if (unbundle(value[1]) === '$type') { - errors = errors.concat(validateEnum({ - key: `${key}[${i}]`, - value: value[i], - valueSpec: styleSpec.geometry_type, - style: options.style, - styleSpec: options.styleSpec - })); + errors = errors.concat( + validateEnum({ + key: `${key}[${i}]`, + value: value[i], + valueSpec: styleSpec.geometry_type, + style: options.style, + styleSpec: options.styleSpec + }) + ); } else if (type !== 'string' && type !== 'number' && type !== 'boolean') { - errors.push(new ValidationError(`${key}[${i}]`, value[i], `string, number, or boolean expected, ${type} found`)); + errors.push( + new ValidationError( + `${key}[${i}]`, + value[i], + `string, number, or boolean expected, ${type} found` + ) + ); } } break; @@ -86,12 +111,14 @@ function validateNonExpressionFilter(options) { case 'all': case 'none': for (let i = 1; i < value.length; i++) { - errors = errors.concat(validateNonExpressionFilter({ - key: `${key}[${i}]`, - value: value[i], - style: options.style, - styleSpec: options.styleSpec - })); + errors = errors.concat( + validateNonExpressionFilter({ + key: `${key}[${i}]`, + value: value[i], + style: options.style, + styleSpec: options.styleSpec + }) + ); } break; @@ -99,9 +126,17 @@ function validateNonExpressionFilter(options) { case '!has': type = getType(value[1]); if (value.length !== 2) { - errors.push(new ValidationError(key, value, `filter array for "${value[0]}" operator must have 2 elements`)); + errors.push( + new ValidationError( + key, + value, + `filter array for "${value[0]}" operator must have 2 elements` + ) + ); } else if (type !== 'string') { - errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`)); + errors.push( + new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`) + ); } break; } diff --git a/src/validate/validate_font_faces.ts b/src/validate/validate_font_faces.ts index 0bdaf54bb..e3206cfd6 100644 --- a/src/validate/validate_font_faces.ts +++ b/src/validate/validate_font_faces.ts @@ -22,13 +22,7 @@ export function validateFontFaces(options: ValidateFontFacesOptions): Validation const style = options.style; if (!isObjectLiteral(value)) { - return [ - new ValidationError( - key, - value, - `object expected, ${getType(value)} found` - ), - ]; + return [new ValidationError(key, value, `object expected, ${getType(value)} found`)]; } const errors: ValidationError[] = []; @@ -39,42 +33,47 @@ export function validateFontFaces(options: ValidateFontFacesOptions): Validation if (fontValueType === 'string') { // Validate as a string URL - errors.push(...validateString({ - key: `${key}.${fontName}`, - value: fontValue, - })); + errors.push( + ...validateString({ + key: `${key}.${fontName}`, + value: fontValue + }) + ); } else if (fontValueType === 'array') { // Validate as an array of font face objects const fontFaceSpec = { url: { type: 'string', - required: true, + required: true }, 'unicode-range': { type: 'array', - value: 'string', + value: 'string' } }; for (const [i, fontFace] of (fontValue as any[]).entries()) { - errors.push(...validateObject({ - key: `${key}.${fontName}[${i}]`, - value: fontFace, - valueSpec: fontFaceSpec, - styleSpec, - style, - validateSpec, - })); + errors.push( + ...validateObject({ + key: `${key}.${fontName}[${i}]`, + value: fontFace, + valueSpec: fontFaceSpec, + styleSpec, + style, + validateSpec + }) + ); } } else { - errors.push(new ValidationError( - `${key}.${fontName}`, - fontValue, - `string or array expected, ${fontValueType} found` - )); + errors.push( + new ValidationError( + `${key}.${fontName}`, + fontValue, + `string or array expected, ${fontValueType} found` + ) + ); } } return errors; } - diff --git a/src/validate/validate_function.ts b/src/validate/validate_function.ts index b81344918..0312c7ed8 100644 --- a/src/validate/validate_function.ts +++ b/src/validate/validate_function.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {getType} from '../util/get_type'; import {validateObject} from './validate_object'; @@ -41,51 +40,80 @@ export function validateFunction(options): Array { }); if (functionType === 'identity' && isZoomFunction) { - errors.push(new ValidationError(options.key, options.value, 'missing required property "property"')); + errors.push( + new ValidationError(options.key, options.value, 'missing required property "property"') + ); } if (functionType !== 'identity' && !options.value.stops) { - errors.push(new ValidationError(options.key, options.value, 'missing required property "stops"')); + errors.push( + new ValidationError(options.key, options.value, 'missing required property "stops"') + ); } - if (functionType === 'exponential' && options.valueSpec.expression && !supportsInterpolation(options.valueSpec)) { - errors.push(new ValidationError(options.key, options.value, 'exponential functions not supported')); + if ( + functionType === 'exponential' && + options.valueSpec.expression && + !supportsInterpolation(options.valueSpec) + ) { + errors.push( + new ValidationError(options.key, options.value, 'exponential functions not supported') + ); } if (options.styleSpec.$version >= 8) { if (isPropertyFunction && !supportsPropertyExpression(options.valueSpec)) { - errors.push(new ValidationError(options.key, options.value, 'property functions not supported')); + errors.push( + new ValidationError(options.key, options.value, 'property functions not supported') + ); } else if (isZoomFunction && !supportsZoomExpression(options.valueSpec)) { - errors.push(new ValidationError(options.key, options.value, 'zoom functions not supported')); + errors.push( + new ValidationError(options.key, options.value, 'zoom functions not supported') + ); } } - if ((functionType === 'categorical' || isZoomAndPropertyFunction) && options.value.property === undefined) { - errors.push(new ValidationError(options.key, options.value, '"property" property is required')); + if ( + (functionType === 'categorical' || isZoomAndPropertyFunction) && + options.value.property === undefined + ) { + errors.push( + new ValidationError(options.key, options.value, '"property" property is required') + ); } return errors; function validateFunctionStops(options) { if (functionType === 'identity') { - return [new ValidationError(options.key, options.value, 'identity function may not have a "stops" property')]; + return [ + new ValidationError( + options.key, + options.value, + 'identity function may not have a "stops" property' + ) + ]; } let errors = []; const value = options.value; - errors = errors.concat(validateArray({ - key: options.key, - value, - valueSpec: options.valueSpec, - validateSpec: options.validateSpec, - style: options.style, - styleSpec: options.styleSpec, - arrayElementValidator: validateFunctionStop - })); + errors = errors.concat( + validateArray({ + key: options.key, + value, + valueSpec: options.valueSpec, + validateSpec: options.validateSpec, + style: options.style, + styleSpec: options.styleSpec, + arrayElementValidator: validateFunctionStop + }) + ); if (getType(value) === 'array' && value.length === 0) { - errors.push(new ValidationError(options.key, value, 'array must have at least one stop')); + errors.push( + new ValidationError(options.key, value, 'array must have at least one stop') + ); } return errors; @@ -101,12 +129,20 @@ export function validateFunction(options): Array { } if (value.length !== 2) { - return [new ValidationError(key, value, `array length 2 expected, length ${value.length} found`)]; + return [ + new ValidationError( + key, + value, + `array length 2 expected, length ${value.length} found` + ) + ]; } if (isZoomAndPropertyFunction) { if (getType(value[0]) !== 'object') { - return [new ValidationError(key, value, `object expected, ${getType(value[0])} found`)]; + return [ + new ValidationError(key, value, `object expected, ${getType(value[0])} found`) + ]; } if (value[0].zoom === undefined) { return [new ValidationError(key, value, 'object stop key must have zoom')]; @@ -115,45 +151,69 @@ export function validateFunction(options): Array { return [new ValidationError(key, value, 'object stop key must have value')]; } if (previousStopDomainZoom && previousStopDomainZoom > unbundle(value[0].zoom)) { - return [new ValidationError(key, value[0].zoom, 'stop zoom values must appear in ascending order')]; + return [ + new ValidationError( + key, + value[0].zoom, + 'stop zoom values must appear in ascending order' + ) + ]; } if (unbundle(value[0].zoom) !== previousStopDomainZoom) { previousStopDomainZoom = unbundle(value[0].zoom); previousStopDomainValue = undefined; stopDomainValues = {}; } - errors = errors.concat(validateObject({ - key: `${key}[0]`, - value: value[0], - valueSpec: {zoom: {}}, - validateSpec: options.validateSpec, - style: options.style, - styleSpec: options.styleSpec, - objectElementValidators: {zoom: validateNumber, value: validateStopDomainValue} - })); + errors = errors.concat( + validateObject({ + key: `${key}[0]`, + value: value[0], + valueSpec: {zoom: {}}, + validateSpec: options.validateSpec, + style: options.style, + styleSpec: options.styleSpec, + objectElementValidators: { + zoom: validateNumber, + value: validateStopDomainValue + } + }) + ); } else { - errors = errors.concat(validateStopDomainValue({ - key: `${key}[0]`, - value: value[0], - valueSpec: {}, - validateSpec: options.validateSpec, - style: options.style, - styleSpec: options.styleSpec - }, value)); + errors = errors.concat( + validateStopDomainValue( + { + key: `${key}[0]`, + value: value[0], + valueSpec: {}, + validateSpec: options.validateSpec, + style: options.style, + styleSpec: options.styleSpec + }, + value + ) + ); } if (isExpression(deepUnbundle(value[1]))) { - return errors.concat([new ValidationError(`${key}[1]`, value[1], 'expressions are not allowed in function stops.')]); + return errors.concat([ + new ValidationError( + `${key}[1]`, + value[1], + 'expressions are not allowed in function stops.' + ) + ]); } - return errors.concat(options.validateSpec({ - key: `${key}[1]`, - value: value[1], - valueSpec: functionValueSpec, - validateSpec: options.validateSpec, - style: options.style, - styleSpec: options.styleSpec - })); + return errors.concat( + options.validateSpec({ + key: `${key}[1]`, + value: value[1], + valueSpec: functionValueSpec, + validateSpec: options.validateSpec, + style: options.style, + styleSpec: options.styleSpec + }) + ); } function validateStopDomainValue(options, stop) { @@ -165,33 +225,65 @@ export function validateFunction(options): Array { if (!stopKeyType) { stopKeyType = type; } else if (type !== stopKeyType) { - return [new ValidationError(options.key, reportValue, `${type} stop domain type must match previous stop domain type ${stopKeyType}`)]; + return [ + new ValidationError( + options.key, + reportValue, + `${type} stop domain type must match previous stop domain type ${stopKeyType}` + ) + ]; } if (type !== 'number' && type !== 'string' && type !== 'boolean') { - return [new ValidationError(options.key, reportValue, 'stop domain value must be a number, string, or boolean')]; + return [ + new ValidationError( + options.key, + reportValue, + 'stop domain value must be a number, string, or boolean' + ) + ]; } if (type !== 'number' && functionType !== 'categorical') { let message = `number expected, ${type} found`; if (supportsPropertyExpression(functionValueSpec) && functionType === undefined) { - message += '\nIf you intended to use a categorical function, specify `"type": "categorical"`.'; + message += + '\nIf you intended to use a categorical function, specify `"type": "categorical"`.'; } return [new ValidationError(options.key, reportValue, message)]; } - if (functionType === 'categorical' && type === 'number' && (!isFinite(value as number) || Math.floor(value as number) !== value)) { - return [new ValidationError(options.key, reportValue, `integer expected, found ${value}`)]; + if ( + functionType === 'categorical' && + type === 'number' && + (!isFinite(value as number) || Math.floor(value as number) !== value) + ) { + return [ + new ValidationError(options.key, reportValue, `integer expected, found ${value}`) + ]; } - if (functionType !== 'categorical' && type === 'number' && previousStopDomainValue !== undefined && value < previousStopDomainValue) { - return [new ValidationError(options.key, reportValue, 'stop domain values must appear in ascending order')]; + if ( + functionType !== 'categorical' && + type === 'number' && + previousStopDomainValue !== undefined && + value < previousStopDomainValue + ) { + return [ + new ValidationError( + options.key, + reportValue, + 'stop domain values must appear in ascending order' + ) + ]; } else { previousStopDomainValue = value; } if (functionType === 'categorical' && (value as any) in stopDomainValues) { - return [new ValidationError(options.key, reportValue, 'stop domain values must be unique')]; + return [ + new ValidationError(options.key, reportValue, 'stop domain values must be unique') + ]; } else { stopDomainValues[value as any] = true; } diff --git a/src/validate/validate_glyphs_url.ts b/src/validate/validate_glyphs_url.ts index 4581edf2f..a6f7aa917 100644 --- a/src/validate/validate_glyphs_url.ts +++ b/src/validate/validate_glyphs_url.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {validateString} from './validate_string'; @@ -10,7 +9,9 @@ export function validateGlyphsUrl(options) { if (errors.length) return errors; if (value.indexOf('{fontstack}') === -1) { - errors.push(new ValidationError(key, value, '"glyphs" url must include a "{fontstack}" token')); + errors.push( + new ValidationError(key, value, '"glyphs" url must include a "{fontstack}" token') + ); } if (value.indexOf('{range}') === -1) { diff --git a/src/validate/validate_layer.test.ts b/src/validate/validate_layer.test.ts index dbdd032b0..f2d837a11 100644 --- a/src/validate/validate_layer.test.ts +++ b/src/validate/validate_layer.test.ts @@ -8,13 +8,13 @@ test.each([ ['string', '__proto__'], ['boolean', true], ['array', [{}]], - ['null', null], + ['null', null] ])('Should return error if layer value is %s instead of an object', (valueType, value) => { const errors = validateLayer({ validateSpec: validate, value: value, styleSpec: v8, - style: {} as any, + style: {} as any }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe(`object expected, ${valueType} found`); diff --git a/src/validate/validate_layer.ts b/src/validate/validate_layer.ts index 1224fdee5..55482ac39 100644 --- a/src/validate/validate_layer.ts +++ b/src/validate/validate_layer.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {unbundle} from '../util/unbundle_jsonlint'; import {validateObject} from './validate_object'; @@ -17,7 +16,7 @@ export function validateLayer(options) { const styleSpec = options.styleSpec; if (getType(layer) !== 'object') { - return [new ValidationError(key, layer, `object expected, ${getType(layer)} found`)] + return [new ValidationError(key, layer, `object expected, ${getType(layer)} found`)]; } if (!layer.type && !layer.ref) { @@ -31,7 +30,13 @@ export function validateLayer(options) { for (let i = 0; i < options.arrayIndex; i++) { const otherLayer = style.layers[i]; if (unbundle(otherLayer.id) === layerId) { - errors.push(new ValidationError(key, layer.id, `duplicate layer id "${layer.id}", previously used at line ${otherLayer.id.__line__}`)); + errors.push( + new ValidationError( + key, + layer.id, + `duplicate layer id "${layer.id}", previously used at line ${otherLayer.id.__line__}` + ) + ); } } } @@ -39,7 +44,9 @@ export function validateLayer(options) { if ('ref' in layer) { ['type', 'source', 'source-layer', 'filter', 'layout'].forEach((p) => { if (p in layer) { - errors.push(new ValidationError(key, layer[p], `"${p}" is prohibited for ref layers`)); + errors.push( + new ValidationError(key, layer[p], `"${p}" is prohibited for ref layers`) + ); } }); @@ -52,7 +59,9 @@ export function validateLayer(options) { if (!parent) { errors.push(new ValidationError(key, layer.ref, `ref layer "${ref}" not found`)); } else if (parent.ref) { - errors.push(new ValidationError(key, layer.ref, 'ref cannot reference another ref layer')); + errors.push( + new ValidationError(key, layer.ref, 'ref cannot reference another ref layer') + ); } else { type = unbundle(parent.type); } @@ -63,84 +72,138 @@ export function validateLayer(options) { const source = style.sources && style.sources[layer.source]; const sourceType = source && unbundle(source.type); if (!source) { - errors.push(new ValidationError(key, layer.source, `source "${layer.source}" not found`)); + errors.push( + new ValidationError(key, layer.source, `source "${layer.source}" not found`) + ); } else if (sourceType === 'vector' && type === 'raster') { - errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster source`)); + errors.push( + new ValidationError( + key, + layer.source, + `layer "${layer.id}" requires a raster source` + ) + ); } else if (sourceType !== 'raster-dem' && type === 'hillshade') { - errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster-dem source`)); + errors.push( + new ValidationError( + key, + layer.source, + `layer "${layer.id}" requires a raster-dem source` + ) + ); } else if (sourceType !== 'raster-dem' && type === 'color-relief') { - errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster-dem source`)); + errors.push( + new ValidationError( + key, + layer.source, + `layer "${layer.id}" requires a raster-dem source` + ) + ); } else if (sourceType === 'raster' && type !== 'raster') { - errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a vector source`)); + errors.push( + new ValidationError( + key, + layer.source, + `layer "${layer.id}" requires a vector source` + ) + ); } else if (sourceType === 'vector' && !layer['source-layer']) { - errors.push(new ValidationError(key, layer, `layer "${layer.id}" must specify a "source-layer"`)); - } else if (sourceType === 'raster-dem' && (type !== 'hillshade' && type !== 'color-relief')) { - errors.push(new ValidationError(key, layer.source, 'raster-dem source can only be used with layer type \'hillshade\' or \'color-relief\'.')); - } else if (type === 'line' && layer.paint && layer.paint['line-gradient'] && - (sourceType !== 'geojson' || !source.lineMetrics)) { - errors.push(new ValidationError(key, layer, `layer "${layer.id}" specifies a line-gradient, which requires a GeoJSON source with \`lineMetrics\` enabled.`)); + errors.push( + new ValidationError( + key, + layer, + `layer "${layer.id}" must specify a "source-layer"` + ) + ); + } else if ( + sourceType === 'raster-dem' && + type !== 'hillshade' && + type !== 'color-relief' + ) { + errors.push( + new ValidationError( + key, + layer.source, + "raster-dem source can only be used with layer type 'hillshade' or 'color-relief'." + ) + ); + } else if ( + type === 'line' && + layer.paint && + layer.paint['line-gradient'] && + (sourceType !== 'geojson' || !source.lineMetrics) + ) { + errors.push( + new ValidationError( + key, + layer, + `layer "${layer.id}" specifies a line-gradient, which requires a GeoJSON source with \`lineMetrics\` enabled.` + ) + ); } } } - errors = errors.concat(validateObject({ - key, - value: layer, - valueSpec: styleSpec.layer, - style: options.style, - styleSpec: options.styleSpec, - validateSpec: options.validateSpec, - objectElementValidators: { - '*'() { - return []; - }, - // We don't want to enforce the spec's `"requires": true` for backward compatibility with refs; - // the actual requirement is validated above. See https://github.com/mapbox/mapbox-gl-js/issues/5772. - type() { - return options.validateSpec({ - key: `${key}.type`, - value: layer.type, - valueSpec: styleSpec.layer.type, - style: options.style, - styleSpec: options.styleSpec, - validateSpec: options.validateSpec, - object: layer, - objectKey: 'type' - }); - }, - filter: validateFilter, - layout(options) { - return validateObject({ - layer, - key: options.key, - value: options.value, - style: options.style, - styleSpec: options.styleSpec, - validateSpec: options.validateSpec, - objectElementValidators: { - '*'(options) { - return validateLayoutProperty(extend({layerType: type}, options)); + errors = errors.concat( + validateObject({ + key, + value: layer, + valueSpec: styleSpec.layer, + style: options.style, + styleSpec: options.styleSpec, + validateSpec: options.validateSpec, + objectElementValidators: { + '*'() { + return []; + }, + // We don't want to enforce the spec's `"requires": true` for backward compatibility with refs; + // the actual requirement is validated above. See https://github.com/mapbox/mapbox-gl-js/issues/5772. + type() { + return options.validateSpec({ + key: `${key}.type`, + value: layer.type, + valueSpec: styleSpec.layer.type, + style: options.style, + styleSpec: options.styleSpec, + validateSpec: options.validateSpec, + object: layer, + objectKey: 'type' + }); + }, + filter: validateFilter, + layout(options) { + return validateObject({ + layer, + key: options.key, + value: options.value, + style: options.style, + styleSpec: options.styleSpec, + validateSpec: options.validateSpec, + objectElementValidators: { + '*'(options) { + return validateLayoutProperty(extend({layerType: type}, options)); + } } - } - }); - }, - paint(options) { - return validateObject({ - layer, - key: options.key, - value: options.value, - style: options.style, - styleSpec: options.styleSpec, - validateSpec: options.validateSpec, - objectElementValidators: { - '*'(options) { - return validatePaintProperty(extend({layerType: type}, options)); + }); + }, + paint(options) { + return validateObject({ + layer, + key: options.key, + value: options.value, + style: options.style, + styleSpec: options.styleSpec, + validateSpec: options.validateSpec, + objectElementValidators: { + '*'(options) { + return validatePaintProperty(extend({layerType: type}, options)); + } } - } - }); + }); + } } - } - })); + }) + ); return errors; } diff --git a/src/validate/validate_layout_property.ts b/src/validate/validate_layout_property.ts index ab18260fa..02f80e27b 100644 --- a/src/validate/validate_layout_property.ts +++ b/src/validate/validate_layout_property.ts @@ -1,4 +1,3 @@ - import {validateProperty} from './validate_property'; export function validateLayoutProperty(options) { diff --git a/src/validate/validate_light.ts b/src/validate/validate_light.ts index 453cd89f0..d180ba46c 100644 --- a/src/validate/validate_light.ts +++ b/src/validate/validate_light.ts @@ -13,33 +13,45 @@ export function validateLight(options) { if (light === undefined) { return errors; } else if (rootType !== 'object') { - errors = errors.concat([new ValidationError('light', light, `object expected, ${rootType} found`)]); + errors = errors.concat([ + new ValidationError('light', light, `object expected, ${rootType} found`) + ]); return errors; } for (const key in light) { const transitionMatch = key.match(/^(.*)-transition$/); - if (transitionMatch && lightSpec[transitionMatch[1]] && lightSpec[transitionMatch[1]].transition) { - errors = errors.concat(options.validateSpec({ - key, - value: light[key], - valueSpec: styleSpec.transition, - validateSpec: options.validateSpec, - style, - styleSpec - })); + if ( + transitionMatch && + lightSpec[transitionMatch[1]] && + lightSpec[transitionMatch[1]].transition + ) { + errors = errors.concat( + options.validateSpec({ + key, + value: light[key], + valueSpec: styleSpec.transition, + validateSpec: options.validateSpec, + style, + styleSpec + }) + ); } else if (lightSpec[key]) { - errors = errors.concat(options.validateSpec({ - key, - value: light[key], - valueSpec: lightSpec[key], - validateSpec: options.validateSpec, - style, - styleSpec - })); + errors = errors.concat( + options.validateSpec({ + key, + value: light[key], + valueSpec: lightSpec[key], + validateSpec: options.validateSpec, + style, + styleSpec + }) + ); } else { - errors = errors.concat([new ValidationError(key, light[key], `unknown property "${key}"`)]); + errors = errors.concat([ + new ValidationError(key, light[key], `unknown property "${key}"`) + ]); } } diff --git a/src/validate/validate_number.ts b/src/validate/validate_number.ts index 44394707e..4753c45d4 100644 --- a/src/validate/validate_number.ts +++ b/src/validate/validate_number.ts @@ -1,4 +1,3 @@ - import {getType} from '../util/get_type'; import {ValidationError} from '../error/validation_error'; @@ -17,11 +16,23 @@ export function validateNumber(options) { } if ('minimum' in valueSpec && value < valueSpec.minimum) { - return [new ValidationError(key, value, `${value} is less than the minimum value ${valueSpec.minimum}`)]; + return [ + new ValidationError( + key, + value, + `${value} is less than the minimum value ${valueSpec.minimum}` + ) + ]; } if ('maximum' in valueSpec && value > valueSpec.maximum) { - return [new ValidationError(key, value, `${value} is greater than the maximum value ${valueSpec.maximum}`)]; + return [ + new ValidationError( + key, + value, + `${value} is greater than the maximum value ${valueSpec.maximum}` + ) + ]; } return []; diff --git a/src/validate/validate_number_array.test.ts b/src/validate/validate_number_array.test.ts index 6c39f6134..7cbb3d694 100644 --- a/src/validate/validate_number_array.test.ts +++ b/src/validate/validate_number_array.test.ts @@ -4,7 +4,11 @@ import {describe, test, expect} from 'vitest'; describe('Validate NumberArray', () => { test('Should return error if type is not number or array', () => { - let errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: '3'}); + let errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: '3' + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray: number expected, string found'); @@ -16,7 +20,11 @@ describe('Validate NumberArray', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray: number expected, null found'); - errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: {x: 1, y: 1}}); + errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: {x: 1, y: 1} + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray: number expected, object found'); @@ -26,11 +34,19 @@ describe('Validate NumberArray', () => { }); test('Should pass if type is number', () => { - const errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: 1}); + const errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: 1 + }); expect(errors).toHaveLength(0); }); test('Should return error if array contains non-numeric values', () => { - let errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: ['1']}); + let errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: ['1'] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray[0]: number expected, string found'); @@ -42,15 +58,27 @@ describe('Validate NumberArray', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray[0]: number expected, NaN found'); - errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: [{x: 1}]}); + errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: [{x: 1}] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray[0]: number expected, object found'); - errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: [1, 3, false]}); + errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: [1, 3, false] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('numberArray[2]: number expected, boolean found'); - errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: ['1', 3, false]}); + errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: ['1', 3, false] + }); expect(errors).toHaveLength(2); expect(errors[0].message).toBe('numberArray[0]: number expected, string found'); expect(errors[1].message).toBe('numberArray[2]: number expected, boolean found'); @@ -59,12 +87,22 @@ describe('Validate NumberArray', () => { test('Should pass if type is numeric array', () => { let errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: []}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('numberArray: array length at least 1 expected, length 0 found'); + expect(errors[0].message).toBe( + 'numberArray: array length at least 1 expected, length 0 found' + ); errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: [1]}); expect(errors).toHaveLength(0); - errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: [1, 1, 1]}); + errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: [1, 1, 1] + }); expect(errors).toHaveLength(0); - errors = validateNumberArray({validateSpec: validate, key: 'numberArray', value: [1, 1, 1, 1, 1, 1, 1, 1]}); + errors = validateNumberArray({ + validateSpec: validate, + key: 'numberArray', + value: [1, 1, 1, 1, 1, 1, 1, 1] + }); expect(errors).toHaveLength(0); }); }); diff --git a/src/validate/validate_number_array.ts b/src/validate/validate_number_array.ts index 234e89170..fc51b0e81 100644 --- a/src/validate/validate_number_array.ts +++ b/src/validate/validate_number_array.ts @@ -8,23 +8,26 @@ export function validateNumberArray(options) { const type = getType(value); if (type === 'array') { - const arrayElementSpec = { type: 'number' }; if (value.length < 1) { - return [new ValidationError(key, value, 'array length at least 1 expected, length 0 found')]; + return [ + new ValidationError(key, value, 'array length at least 1 expected, length 0 found') + ]; } let errors = []; for (let i = 0; i < value.length; i++) { - errors = errors.concat(options.validateSpec({ - key: `${key}[${i}]`, - value: value[i], - validateSpec: options.validateSpec, - valueSpec: arrayElementSpec - })); + errors = errors.concat( + options.validateSpec({ + key: `${key}[${i}]`, + value: value[i], + validateSpec: options.validateSpec, + valueSpec: arrayElementSpec + }) + ); } return errors; } else { diff --git a/src/validate/validate_object.test.ts b/src/validate/validate_object.test.ts index bd351f34e..d7b24fcf0 100644 --- a/src/validate/validate_object.test.ts +++ b/src/validate/validate_object.test.ts @@ -11,17 +11,17 @@ describe('Validate object', () => { __defineProperty__: 123, hasOwnProperty: 123, toLocaleString: 123, - valueOf: 123, + valueOf: 123 }, style: {}, styleSpec: v8, - validateSpec: validate, + validateSpec: validate }); expect(errors).toEqual([ {message: 'test: unknown property "__defineProperty__"'}, {message: 'test: unknown property "hasOwnProperty"'}, {message: 'test: unknown property "toLocaleString"'}, - {message: 'test: unknown property "valueOf"'}, + {message: 'test: unknown property "valueOf"'} ]); }); @@ -33,18 +33,18 @@ describe('Validate object', () => { '__defineProperty__.__defineProperty__': 123, 'hasOwnProperty.hasOwnProperty': 123, 'toLocaleString.toLocaleString': 123, - 'valueOf.valueOf': 123, + 'valueOf.valueOf': 123 }, style: {}, styleSpec: v8, - validateSpec: validate, + validateSpec: validate }); expect(errors).toEqual([ {message: 'test: unknown property "__proto__.__proto__"'}, {message: 'test: unknown property "__defineProperty__.__defineProperty__"'}, {message: 'test: unknown property "hasOwnProperty.hasOwnProperty"'}, {message: 'test: unknown property "toLocaleString.toLocaleString"'}, - {message: 'test: unknown property "valueOf.valueOf"'}, + {message: 'test: unknown property "valueOf.valueOf"'} ]); }); }); diff --git a/src/validate/validate_object.ts b/src/validate/validate_object.ts index fb6e9f721..e6e60b113 100644 --- a/src/validate/validate_object.ts +++ b/src/validate/validate_object.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {getOwn} from '../util/get_own'; import {getType} from '../util/get_type'; @@ -33,20 +32,27 @@ export function validateObject(options): Array { } else if (elementSpecs['*']) { validateElement = validateSpec; } else { - errors.push(new ValidationError(key, object[objectKey], `unknown property "${objectKey}"`)); + errors.push( + new ValidationError(key, object[objectKey], `unknown property "${objectKey}"`) + ); continue; } - errors = errors.concat(validateElement({ - key: (key ? `${key}.` : key) + objectKey, - value: object[objectKey], - valueSpec: elementSpec, - style, - styleSpec, - object, - objectKey, - validateSpec, - }, object)); + errors = errors.concat( + validateElement( + { + key: (key ? `${key}.` : key) + objectKey, + value: object[objectKey], + valueSpec: elementSpec, + style, + styleSpec, + object, + objectKey, + validateSpec + }, + object + ) + ); } for (const elementSpecKey in elementSpecs) { @@ -55,8 +61,14 @@ export function validateObject(options): Array { continue; } - if (elementSpecs[elementSpecKey].required && elementSpecs[elementSpecKey]['default'] === undefined && object[elementSpecKey] === undefined) { - errors.push(new ValidationError(key, object, `missing required property "${elementSpecKey}"`)); + if ( + elementSpecs[elementSpecKey].required && + elementSpecs[elementSpecKey]['default'] === undefined && + object[elementSpecKey] === undefined + ) { + errors.push( + new ValidationError(key, object, `missing required property "${elementSpecKey}"`) + ); } } diff --git a/src/validate/validate_padding.test.ts b/src/validate/validate_padding.test.ts index db67fe675..e09b8e39a 100644 --- a/src/validate/validate_padding.test.ts +++ b/src/validate/validate_padding.test.ts @@ -35,11 +35,19 @@ describe('Validate Padding', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('padding: padding requires 1 to 4 values; 0 values found'); - errors = validatePadding({validateSpec: validate, key: 'padding', value: [1, 1, 1, 1, 1]}); + errors = validatePadding({ + validateSpec: validate, + key: 'padding', + value: [1, 1, 1, 1, 1] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('padding: padding requires 1 to 4 values; 5 values found'); - errors = validatePadding({validateSpec: validate, key: 'padding', value: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}); + errors = validatePadding({ + validateSpec: validate, + key: 'padding', + value: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('padding: padding requires 1 to 4 values; 12 values found'); }); @@ -65,7 +73,11 @@ describe('Validate Padding', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('padding[2]: number expected, boolean found'); - errors = validatePadding({validateSpec: validate, key: 'padding', value: ['1', 3, false]}); + errors = validatePadding({ + validateSpec: validate, + key: 'padding', + value: ['1', 3, false] + }); expect(errors).toHaveLength(2); expect(errors[0].message).toBe('padding[0]: number expected, string found'); expect(errors[1].message).toBe('padding[2]: number expected, boolean found'); diff --git a/src/validate/validate_padding.ts b/src/validate/validate_padding.ts index 9f4989755..0b093f6c5 100644 --- a/src/validate/validate_padding.ts +++ b/src/validate/validate_padding.ts @@ -9,7 +9,13 @@ export function validatePadding(options) { if (type === 'array') { if (value.length < 1 || value.length > 4) { - return [new ValidationError(key, value, `padding requires 1 to 4 values; ${value.length} values found`)]; + return [ + new ValidationError( + key, + value, + `padding requires 1 to 4 values; ${value.length} values found` + ) + ]; } const arrayElementSpec = { @@ -18,12 +24,14 @@ export function validatePadding(options) { let errors = []; for (let i = 0; i < value.length; i++) { - errors = errors.concat(options.validateSpec({ - key: `${key}[${i}]`, - value: value[i], - validateSpec: options.validateSpec, - valueSpec: arrayElementSpec - })); + errors = errors.concat( + options.validateSpec({ + key: `${key}[${i}]`, + value: value[i], + validateSpec: options.validateSpec, + valueSpec: arrayElementSpec + }) + ); } return errors; } else { diff --git a/src/validate/validate_paint_property.ts b/src/validate/validate_paint_property.ts index 74e62ffd3..6fbeee586 100644 --- a/src/validate/validate_paint_property.ts +++ b/src/validate/validate_paint_property.ts @@ -1,4 +1,3 @@ - import {validateProperty} from './validate_property'; export function validatePaintProperty(options) { diff --git a/src/validate/validate_projection.test.ts b/src/validate/validate_projection.test.ts index b283dd511..599ec1f89 100644 --- a/src/validate/validate_projection.test.ts +++ b/src/validate/validate_projection.test.ts @@ -6,53 +6,107 @@ import {describe, test, expect, it} from 'vitest'; describe('Validate projection', () => { it('Should pass when value is undefined', () => { - const errors = validateProjection({validateSpec: validate, value: undefined, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: undefined, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('Should return error when value is not an object', () => { - const errors = validateProjection({validateSpec: validate, value: '' as unknown as ProjectionSpecification, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: '' as unknown as ProjectionSpecification, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('object'); expect(errors[0].message).toContain('expected'); }); test('Should return error in case of unknown property', () => { - const errors = validateProjection({validateSpec: validate, value: {a: 1} as any, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: {a: 1} as any, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('a: unknown property \"a\"'); }); test('Should return errors according to spec violations', () => { - const errors = validateProjection({validateSpec: validate, value: {type: 1 as any}, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: {type: 1 as any}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('type: projection expected, invalid type \"number\" found'); }); test('Should return error when value is null', () => { - const errors = validateProjection({validateSpec: validate, value: null as any, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: null as any, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('projection: object expected, null found'); }); test('Should pass step function', () => { - const errors = validateProjection({validateSpec: validate, value: {'type': ['step', ['zoom'], 'vertical-perspective', 10, 'mercator']}, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: {type: ['step', ['zoom'], 'vertical-perspective', 10, 'mercator']}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('Should pass string value', () => { - const errors = validateProjection({validateSpec: validate, value: {'type': 'mercator'}, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: {type: 'mercator'}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('Should pass if [proj, proj, number]', () => { - const errors = validateProjection({validateSpec: validate, value: {'type': ['mercator', 'mercator', 0.3]}, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: {type: ['mercator', 'mercator', 0.3]}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('should parse interpolate', () => { - const errors = validateProjection({validateSpec: validate, value: {'type': ['interpolate', ['linear'], ['zoom'], 0, 'mercator', 5, 'vertical-perspective']}, styleSpec: v8, style: {} as any}); + const errors = validateProjection({ + validateSpec: validate, + value: { + type: [ + 'interpolate', + ['linear'], + ['zoom'], + 0, + 'mercator', + 5, + 'vertical-perspective' + ] + }, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); - }); diff --git a/src/validate/validate_projection.ts b/src/validate/validate_projection.ts index 4f216baa2..9835db3f7 100644 --- a/src/validate/validate_projection.ts +++ b/src/validate/validate_projection.ts @@ -21,21 +21,27 @@ export function validateProjection(options: ValidateProjectionOptions) { if (projection === undefined) { return []; } else if (rootType !== 'object') { - return [new ValidationError('projection', projection, `object expected, ${rootType} found`)]; + return [ + new ValidationError('projection', projection, `object expected, ${rootType} found`) + ]; } let errors = []; for (const key in projection) { if (projectionSpec[key]) { - errors = errors.concat(options.validateSpec({ - key, - value: projection[key], - valueSpec: projectionSpec[key], - style, - styleSpec - })); + errors = errors.concat( + options.validateSpec({ + key, + value: projection[key], + valueSpec: projectionSpec[key], + style, + styleSpec + }) + ); } else { - errors = errors.concat([new ValidationError(key, projection[key], `unknown property "${key}"`)]); + errors = errors.concat([ + new ValidationError(key, projection[key], `unknown property "${key}"`) + ]); } } diff --git a/src/validate/validate_projectiondefinition.test.ts b/src/validate/validate_projectiondefinition.test.ts index ebe203a7d..829dc4985 100644 --- a/src/validate/validate_projectiondefinition.test.ts +++ b/src/validate/validate_projectiondefinition.test.ts @@ -2,33 +2,32 @@ import {validateProjectionDefinition} from './validate_projectiondefinition'; import {describe, test, expect} from 'vitest'; describe('validateProjection function', () => { - const key = 'sample_projection_key'; test('should return error when projection is not a string or array', () => { expect(validateProjectionDefinition({key, value: ''})).toHaveLength(0); expect(validateProjectionDefinition({key, value: 0})).toMatchObject([ - {message: `${key}: projection expected, invalid type "number" found`}, + {message: `${key}: projection expected, invalid type "number" found`} ]); expect(validateProjectionDefinition({key, value: {}})).toMatchObject([ - {message: `${key}: projection expected, invalid type "object" found`}, + {message: `${key}: projection expected, invalid type "object" found`} ]); expect(validateProjectionDefinition({key, value: false})).toMatchObject([ - {message: `${key}: projection expected, invalid type "boolean" found`}, + {message: `${key}: projection expected, invalid type "boolean" found`} ]); expect(validateProjectionDefinition({key, value: null})).toMatchObject([ - {message: `${key}: projection expected, invalid type "null" found`}, + {message: `${key}: projection expected, invalid type "null" found`} ]); expect(validateProjectionDefinition({key, value: undefined})).toMatchObject([ - {message: `${key}: projection expected, invalid type "undefined" found`}, + {message: `${key}: projection expected, invalid type "undefined" found`} ]); }); - + test('Should error when projection is an invalid projection transition', () => { const errors = validateProjectionDefinition({value: [3, 'mercator', 0.3], key}); expect(errors).toMatchObject([ - {message: `${key}: projection expected, invalid array [3,\"mercator\",0.3] found`}, + {message: `${key}: projection expected, invalid array [3,\"mercator\",0.3] found`} ]); }); @@ -43,18 +42,26 @@ describe('validateProjection function', () => { }); test('Should return no errors when projection is valid interpolation-projection expression', () => { - const errors = validateProjectionDefinition({value: ['interpolate', ['linear'], ['zoom'], 0, 'mercator', 5, 'vertical-perspective'], key}); + const errors = validateProjectionDefinition({ + value: ['interpolate', ['linear'], ['zoom'], 0, 'mercator', 5, 'vertical-perspective'], + key + }); expect(errors).toHaveLength(0); }); test('Should return no errors when projection is valid step expression', () => { - const errors = validateProjectionDefinition({value: ['step', ['zoom'], 'vertical-perspective', 10, 'mercator'], key}); + const errors = validateProjectionDefinition({ + value: ['step', ['zoom'], 'vertical-perspective', 10, 'mercator'], + key + }); expect(errors).toHaveLength(0); }); test('Should return no errors when projection is valid step expression with a transition', () => { - const errors = validateProjectionDefinition({value: ['step', ['zoom'], ['vertical-perspective', 'mercator', 0.5], 10, 'mercator'], key}); + const errors = validateProjectionDefinition({ + value: ['step', ['zoom'], ['vertical-perspective', 'mercator', 0.5], 10, 'mercator'], + key + }); expect(errors).toHaveLength(0); }); - }); diff --git a/src/validate/validate_projectiondefinition.ts b/src/validate/validate_projectiondefinition.ts index 499a46c77..e4da636a0 100644 --- a/src/validate/validate_projectiondefinition.ts +++ b/src/validate/validate_projectiondefinition.ts @@ -3,34 +3,48 @@ import {ProjectionDefinitionT, PropertyValueSpecification} from '../types.g'; import {getType} from '../util/get_type'; export function validateProjectionDefinition(options) { - const key = options.key; let value = options.value; value = value instanceof String ? value.valueOf() : value; const type = getType(value); - if (type === 'array' && !isProjectionDefinitionValue(value) && !isPropertyValueSpecification(value)) { - return [new ValidationError(key, value, `projection expected, invalid array ${JSON.stringify(value)} found`)]; - } else if (!['array', 'string'].includes(type)) { - return [new ValidationError(key, value, `projection expected, invalid type "${type}" found`)]; + if ( + type === 'array' && + !isProjectionDefinitionValue(value) && + !isPropertyValueSpecification(value) + ) { + return [ + new ValidationError( + key, + value, + `projection expected, invalid array ${JSON.stringify(value)} found` + ) + ]; + } else if (!['array', 'string'].includes(type)) { + return [ + new ValidationError(key, value, `projection expected, invalid type "${type}" found`) + ]; } return []; } -function isPropertyValueSpecification(value: unknown): value is PropertyValueSpecification { - +function isPropertyValueSpecification( + value: unknown +): value is PropertyValueSpecification { if (['interpolate', 'step', 'literal'].includes(value[0])) { - return true + return true; } - return false + return false; } function isProjectionDefinitionValue(value: unknown): value is ProjectionDefinitionT { - return Array.isArray(value) && + return ( + Array.isArray(value) && value.length === 3 && typeof value[0] === 'string' && typeof value[1] === 'string' && - typeof value[2] === 'number'; -} \ No newline at end of file + typeof value[2] === 'number' + ); +} diff --git a/src/validate/validate_property.ts b/src/validate/validate_property.ts index 29ff4e8b4..e52da1703 100644 --- a/src/validate/validate_property.ts +++ b/src/validate/validate_property.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {getType} from '../util/get_type'; import {isFunction} from '../function'; @@ -17,7 +16,12 @@ export function validateProperty(options, propertyType) { if (!layerSpec) return []; const transitionMatch = propertyKey.match(/^(.*)-transition$/); - if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]] && layerSpec[transitionMatch[1]].transition) { + if ( + propertyType === 'paint' && + transitionMatch && + layerSpec[transitionMatch[1]] && + layerSpec[transitionMatch[1]].transition + ) { return validateSpec({ key, value, @@ -33,29 +37,46 @@ export function validateProperty(options, propertyType) { } let tokenMatch; - if (getType(value) === 'string' && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = /^{([^}]+)}$/.exec(value))) { - return [new ValidationError( - key, value, - `"${propertyKey}" does not support interpolation syntax\n` + - `Use an identity property function instead: \`{ "type": "identity", "property": ${JSON.stringify(tokenMatch[1])} }\`.`)]; + if ( + getType(value) === 'string' && + supportsPropertyExpression(valueSpec) && + !valueSpec.tokens && + (tokenMatch = /^{([^}]+)}$/.exec(value)) + ) { + return [ + new ValidationError( + key, + value, + `"${propertyKey}" does not support interpolation syntax\n` + + `Use an identity property function instead: \`{ "type": "identity", "property": ${JSON.stringify(tokenMatch[1])} }\`.` + ) + ]; } const errors = []; if (options.layerType === 'symbol') { - if (propertyKey === 'text-font' && isFunction(deepUnbundle(value)) && unbundle(value.type) === 'identity') { - errors.push(new ValidationError(key, value, '"text-font" does not support identity functions')); + if ( + propertyKey === 'text-font' && + isFunction(deepUnbundle(value)) && + unbundle(value.type) === 'identity' + ) { + errors.push( + new ValidationError(key, value, '"text-font" does not support identity functions') + ); } } - return errors.concat(validateSpec({ - key: options.key, - value, - valueSpec, - style, - styleSpec, - expressionContext: 'property', - propertyType, - propertyKey - })); + return errors.concat( + validateSpec({ + key: options.key, + value, + valueSpec, + style, + styleSpec, + expressionContext: 'property', + propertyType, + propertyKey + }) + ); } diff --git a/src/validate/validate_raster_dem_source.test.ts b/src/validate/validate_raster_dem_source.test.ts index 740dff179..b00eee4e3 100644 --- a/src/validate/validate_raster_dem_source.test.ts +++ b/src/validate/validate_raster_dem_source.test.ts @@ -11,26 +11,46 @@ function checkErrorMessage(message: string, key: string, expectedType: string, f describe('Validate source_raster_dem', () => { test('Should pass when value is undefined', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: undefined, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: undefined, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('Should return error when value is not an object', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: '' as unknown as RasterDEMSourceSpecification, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: '' as unknown as RasterDEMSourceSpecification, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('object'); expect(errors[0].message).toContain('expected'); }); test('Should return error in case of unknown property', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: {a: 1} as any, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: {a: 1} as any, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('a'); expect(errors[0].message).toContain('unknown'); }); test('Should return errors according to spec violations', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: {type: 'raster-dem', url: {} as any, tiles: {} as any, encoding: 'foo' as any}, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: {type: 'raster-dem', url: {} as any, tiles: {} as any, encoding: 'foo' as any}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(3); checkErrorMessage(errors[0].message, 'url', 'string', 'object'); checkErrorMessage(errors[1].message, 'tiles', 'array', 'object'); @@ -38,7 +58,19 @@ describe('Validate source_raster_dem', () => { }); test('Should return errors when custom encoding values are set but encoding is "mapbox"', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: {type: 'raster-dem', encoding: 'mapbox', 'redFactor': 1.0, 'greenFactor': 1.0, 'blueFactor': 1.0, 'baseShift': 1.0}, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: { + type: 'raster-dem', + encoding: 'mapbox', + redFactor: 1.0, + greenFactor: 1.0, + blueFactor: 1.0, + baseShift: 1.0 + }, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(4); checkErrorMessage(errors[0].message, 'redFactor', 'custom', 'mapbox'); checkErrorMessage(errors[1].message, 'greenFactor', 'custom', 'mapbox'); @@ -47,7 +79,19 @@ describe('Validate source_raster_dem', () => { }); test('Should return errors when custom encoding values are set but encoding is "terrarium"', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: {type: 'raster-dem', encoding: 'terrarium', 'redFactor': 1.0, 'greenFactor': 1.0, 'blueFactor': 1.0, 'baseShift': 1.0}, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: { + type: 'raster-dem', + encoding: 'terrarium', + redFactor: 1.0, + greenFactor: 1.0, + blueFactor: 1.0, + baseShift: 1.0 + }, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(4); checkErrorMessage(errors[0].message, 'redFactor', 'custom', 'terrarium'); checkErrorMessage(errors[1].message, 'greenFactor', 'custom', 'terrarium'); @@ -56,12 +100,29 @@ describe('Validate source_raster_dem', () => { }); test('Should pass when custom encoding values are set and encoding is "custom"', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: {type: 'raster-dem', encoding: 'custom', 'redFactor': 1.0, 'greenFactor': 1.0, 'blueFactor': 1.0, 'baseShift': 1.0}, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: { + type: 'raster-dem', + encoding: 'custom', + redFactor: 1.0, + greenFactor: 1.0, + blueFactor: 1.0, + baseShift: 1.0 + }, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('Should pass if everything is according to spec', () => { - const errors = validateRasterDEMSource({validateSpec: validate, value: {type: 'raster-dem'}, styleSpec: v8, style: {} as any}); + const errors = validateRasterDEMSource({ + validateSpec: validate, + value: {type: 'raster-dem'}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); }); diff --git a/src/validate/validate_raster_dem_source.ts b/src/validate/validate_raster_dem_source.ts index fe0a7fcc2..c25463123 100644 --- a/src/validate/validate_raster_dem_source.ts +++ b/src/validate/validate_raster_dem_source.ts @@ -15,7 +15,6 @@ interface ValidateRasterDENSourceOptions { export function validateRasterDEMSource( options: ValidateRasterDENSourceOptions ): ValidationError[] { - const sourceName = options.sourceName ?? ''; const rasterDEM = options.value; const styleSpec = options.styleSpec; @@ -28,7 +27,13 @@ export function validateRasterDEMSource( if (rasterDEM === undefined) { return errors; } else if (rootType !== 'object') { - errors.push(new ValidationError('source_raster_dem', rasterDEM, `object expected, ${rootType} found`)); + errors.push( + new ValidationError( + 'source_raster_dem', + rasterDEM, + `object expected, ${rootType} found` + ) + ); return errors; } @@ -39,16 +44,24 @@ export function validateRasterDEMSource( for (const key in rasterDEM) { if (!isCustomEncoding && customEncodingKeys.includes(key)) { - errors.push(new ValidationError(key, rasterDEM[key], `In "${sourceName}": "${key}" is only valid when "encoding" is set to "custom". ${encodingName} encoding found`)); + errors.push( + new ValidationError( + key, + rasterDEM[key], + `In "${sourceName}": "${key}" is only valid when "encoding" is set to "custom". ${encodingName} encoding found` + ) + ); } else if (rasterDEMSpec[key]) { - errors = errors.concat(options.validateSpec({ - key, - value: rasterDEM[key], - valueSpec: rasterDEMSpec[key], - validateSpec: options.validateSpec, - style, - styleSpec - })); + errors = errors.concat( + options.validateSpec({ + key, + value: rasterDEM[key], + valueSpec: rasterDEMSpec[key], + validateSpec: options.validateSpec, + style, + styleSpec + }) + ); } else { errors.push(new ValidationError(key, rasterDEM[key], `unknown property "${key}"`)); } diff --git a/src/validate/validate_sky.test.ts b/src/validate/validate_sky.test.ts index f99ab3ae9..02048a3ae 100644 --- a/src/validate/validate_sky.test.ts +++ b/src/validate/validate_sky.test.ts @@ -6,26 +6,51 @@ import {describe, test, expect, it} from 'vitest'; describe('Validate sky', () => { it('Should pass when value is undefined', () => { - const errors = validateSky({validateSpec: validate, value: undefined, styleSpec: v8, style: {} as any}); + const errors = validateSky({ + validateSpec: validate, + value: undefined, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); test('Should return error when value is not an object', () => { - const errors = validateSky({validateSpec: validate, value: '' as unknown as SkySpecification, styleSpec: v8, style: {} as any}); + const errors = validateSky({ + validateSpec: validate, + value: '' as unknown as SkySpecification, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('object'); expect(errors[0].message).toContain('expected'); }); test('Should return error in case of unknown property', () => { - const errors = validateSky({validateSpec: validate, value: {a: 1} as any, styleSpec: v8, style: {} as any}); + const errors = validateSky({ + validateSpec: validate, + value: {a: 1} as any, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('a'); expect(errors[0].message).toContain('unknown'); }); test('Should return errors according to spec violations', () => { - const errors = validateSky({validateSpec: validate, value: {'sky-color': 1 as any, 'fog-color': 2 as any, 'horizon-fog-blend': {} as any, 'fog-ground-blend': 'foo' as any}, styleSpec: v8, style: {} as any}); + const errors = validateSky({ + validateSpec: validate, + value: { + 'sky-color': 1 as any, + 'fog-color': 2 as any, + 'horizon-fog-blend': {} as any, + 'fog-ground-blend': 'foo' as any + }, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(4); expect(errors[0].message).toBe('sky-color: color expected, number found'); expect(errors[1].message).toBe('fog-color: color expected, number found'); @@ -34,8 +59,17 @@ describe('Validate sky', () => { }); test('Should pass if everything is according to spec', () => { - const errors = validateSky({validateSpec: validate, value: {'sky-color': 'red', 'fog-color': '#123456', 'horizon-fog-blend': 1, 'fog-ground-blend': 0}, styleSpec: v8, style: {} as any}); + const errors = validateSky({ + validateSpec: validate, + value: { + 'sky-color': 'red', + 'fog-color': '#123456', + 'horizon-fog-blend': 1, + 'fog-ground-blend': 0 + }, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); }); - diff --git a/src/validate/validate_sky.ts b/src/validate/validate_sky.ts index a4fb22345..46e6a226e 100644 --- a/src/validate/validate_sky.ts +++ b/src/validate/validate_sky.ts @@ -27,15 +27,19 @@ export function validateSky(options: ValidateSkyOptions) { let errors = []; for (const key in sky) { if (skySpec[key]) { - errors = errors.concat(options.validateSpec({ - key, - value: sky[key], - valueSpec: skySpec[key], - style, - styleSpec - })); + errors = errors.concat( + options.validateSpec({ + key, + value: sky[key], + valueSpec: skySpec[key], + style, + styleSpec + }) + ); } else { - errors = errors.concat([new ValidationError(key, sky[key], `unknown property "${key}"`)]); + errors = errors.concat([ + new ValidationError(key, sky[key], `unknown property "${key}"`) + ]); } } diff --git a/src/validate/validate_source.ts b/src/validate/validate_source.ts index c4281adf8..bb56d1cc2 100644 --- a/src/validate/validate_source.ts +++ b/src/validate/validate_source.ts @@ -1,4 +1,3 @@ - import {ValidationError} from '../error/validation_error'; import {unbundle} from '../util/unbundle_jsonlint'; import {validateObject} from './validate_object'; @@ -36,7 +35,7 @@ export function validateSource(options) { style: options.style, styleSpec, objectElementValidators, - validateSpec, + validateSpec }); return errors; case 'raster-dem': @@ -45,7 +44,7 @@ export function validateSource(options) { value, style: options.style, styleSpec, - validateSpec, + validateSpec }); return errors; @@ -62,20 +61,27 @@ export function validateSource(options) { if (value.cluster) { for (const prop in value.clusterProperties) { const [operator, mapExpr] = value.clusterProperties[prop]; - const reduceExpr = typeof operator === 'string' ? [operator, ['accumulated'], ['get', prop]] : operator; + const reduceExpr = + typeof operator === 'string' + ? [operator, ['accumulated'], ['get', prop]] + : operator; - errors.push(...validateExpression({ - key: `${key}.${prop}.map`, - value: mapExpr, - validateSpec, - expressionContext: 'cluster-map' - })); - errors.push(...validateExpression({ - key: `${key}.${prop}.reduce`, - value: reduceExpr, - validateSpec, - expressionContext: 'cluster-reduce' - })); + errors.push( + ...validateExpression({ + key: `${key}.${prop}.map`, + value: mapExpr, + validateSpec, + expressionContext: 'cluster-map' + }) + ); + errors.push( + ...validateExpression({ + key: `${key}.${prop}.reduce`, + value: reduceExpr, + validateSpec, + expressionContext: 'cluster-reduce' + }) + ); } } return errors; @@ -101,13 +107,22 @@ export function validateSource(options) { }); case 'canvas': - return [new ValidationError(key, null, 'Please use runtime APIs to add canvas sources, rather than including them in stylesheets.', 'source.canvas')]; + return [ + new ValidationError( + key, + null, + 'Please use runtime APIs to add canvas sources, rather than including them in stylesheets.', + 'source.canvas' + ) + ]; default: return validateEnum({ key: `${key}.type`, value: value.type, - valueSpec: {values: ['vector', 'raster', 'raster-dem', 'geojson', 'video', 'image']}, + valueSpec: { + values: ['vector', 'raster', 'raster-dem', 'geojson', 'video', 'image'] + }, style, validateSpec, styleSpec diff --git a/src/validate/validate_sprite.test.ts b/src/validate/validate_sprite.test.ts index 5cb2d8bda..30f7ebd8c 100644 --- a/src/validate/validate_sprite.test.ts +++ b/src/validate/validate_sprite.test.ts @@ -39,38 +39,73 @@ describe('Validate Sprite', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('sprite[0]: object expected, number found'); - errors = validateSprite({validateSpec: validate, key: 'sprite', value: [{id: 'id1', url: 'url1'}, {id: 'id2', url: 'url2'}, false]}); + errors = validateSprite({ + validateSpec: validate, + key: 'sprite', + value: [{id: 'id1', url: 'url1'}, {id: 'id2', url: 'url2'}, false] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('sprite[2]: object expected, boolean found'); - errors = validateSprite({validateSpec: validate, key: 'sprite', value: ['string', {id: 'id1', url: 'url1'}, {id: 'id2', url: 'url2'}, false]}); + errors = validateSprite({ + validateSpec: validate, + key: 'sprite', + value: ['string', {id: 'id1', url: 'url1'}, {id: 'id2', url: 'url2'}, false] + }); expect(errors).toHaveLength(2); expect(errors[0].message).toBe('sprite[0]: object expected, string found'); expect(errors[1].message).toBe('sprite[3]: object expected, boolean found'); }); - test('Should return error if array\'s objects are not of form {id: string, url: string}', () => { - let errors = validateSprite({validateSpec: validate, key: 'sprite', value: [{id: 2, url: 2}]}); + test("Should return error if array's objects are not of form {id: string, url: string}", () => { + let errors = validateSprite({ + validateSpec: validate, + key: 'sprite', + value: [{id: 2, url: 2}] + }); expect(errors).toHaveLength(2); expect(errors[0].message).toBe('sprite[0].id: string expected, number found'); expect(errors[1].message).toBe('sprite[0].url: string expected, number found'); - errors = validateSprite({validateSpec: validate, key: 'sprite', value: [{test: 'string'}]}); + errors = validateSprite({ + validateSpec: validate, + key: 'sprite', + value: [{test: 'string'}] + }); expect(errors).toHaveLength(3); expect(errors[0].message).toBe('sprite[0]: unknown property "test"'); expect(errors[1].message).toBe('sprite[0]: missing required property "id"'); expect(errors[2].message).toBe('sprite[0]: missing required property "url"'); }); - test('Should return error if array\'s objects contain duplicated IDs or URLs', () => { - const errors = validateSprite({validateSpec: validate, key: 'sprite', value: [{id: 'id1', url: 'url1'}, {id: 'id1', url: 'url1'}]}); + test("Should return error if array's objects contain duplicated IDs or URLs", () => { + const errors = validateSprite({ + validateSpec: validate, + key: 'sprite', + value: [ + {id: 'id1', url: 'url1'}, + {id: 'id1', url: 'url1'} + ] + }); expect(errors).toHaveLength(2); - expect(errors[0].message).toBe('sprite: all the sprites\' ids must be unique, but id1 is duplicated'); - expect(errors[1].message).toBe('sprite: all the sprites\' URLs must be unique, but url1 is duplicated'); + expect(errors[0].message).toBe( + "sprite: all the sprites' ids must be unique, but id1 is duplicated" + ); + expect(errors[1].message).toBe( + "sprite: all the sprites' URLs must be unique, but url1 is duplicated" + ); }); test('Should pass if correct array of objects', () => { - const errors = validateSprite({validateSpec: validate, key: 'sprite', value: [{id: 'id1', url: 'url1'}, {id: 'id2', url: 'url2'}, {id: 'id3', url: 'url3'}]}); + const errors = validateSprite({ + validateSpec: validate, + key: 'sprite', + value: [ + {id: 'id1', url: 'url1'}, + {id: 'id2', url: 'url2'}, + {id: 'id3', url: 'url3'} + ] + }); expect(errors).toHaveLength(0); }); }); diff --git a/src/validate/validate_sprite.ts b/src/validate/validate_sprite.ts index 4ff67d84f..066b02c5d 100644 --- a/src/validate/validate_sprite.ts +++ b/src/validate/validate_sprite.ts @@ -19,35 +19,50 @@ export function validateSprite(options: ValidateSpriteOptions) { key, value: sprite }); - } else { const allSpriteIds = []; const allSpriteURLs = []; for (const i in sprite) { - if (sprite[i].id && allSpriteIds.includes(sprite[i].id)) errors.push(new ValidationError(key, sprite, `all the sprites' ids must be unique, but ${sprite[i].id} is duplicated`)); + if (sprite[i].id && allSpriteIds.includes(sprite[i].id)) + errors.push( + new ValidationError( + key, + sprite, + `all the sprites' ids must be unique, but ${sprite[i].id} is duplicated` + ) + ); allSpriteIds.push(sprite[i].id); - if (sprite[i].url && allSpriteURLs.includes(sprite[i].url)) errors.push(new ValidationError(key, sprite, `all the sprites' URLs must be unique, but ${sprite[i].url} is duplicated`)); + if (sprite[i].url && allSpriteURLs.includes(sprite[i].url)) + errors.push( + new ValidationError( + key, + sprite, + `all the sprites' URLs must be unique, but ${sprite[i].url} is duplicated` + ) + ); allSpriteURLs.push(sprite[i].url); const pairSpec = { id: { type: 'string', - required: true, + required: true }, url: { type: 'string', - required: true, + required: true } }; - errors = errors.concat(validateObject({ - key: `${key}[${i}]`, - value: sprite[i], - valueSpec: pairSpec, - validateSpec: options.validateSpec, - })); + errors = errors.concat( + validateObject({ + key: `${key}[${i}]`, + value: sprite[i], + valueSpec: pairSpec, + validateSpec: options.validateSpec + }) + ); } return errors; diff --git a/src/validate/validate_state.ts b/src/validate/validate_state.ts index ccefd9e94..8652eae7e 100644 --- a/src/validate/validate_state.ts +++ b/src/validate/validate_state.ts @@ -14,7 +14,7 @@ export function validateState(options: ValidateStateOptions): ValidationError[] options.key, options.value, `object expected, ${getType(options.value)} found` - ), + ) ]; } diff --git a/src/validate/validate_string.ts b/src/validate/validate_string.ts index 493bb064a..b3c94b61c 100644 --- a/src/validate/validate_string.ts +++ b/src/validate/validate_string.ts @@ -1,4 +1,3 @@ - import {getType} from '../util/get_type'; import {ValidationError} from '../error/validation_error'; diff --git a/src/validate/validate_terrain.test.ts b/src/validate/validate_terrain.test.ts index 9825d0c94..5ee0fb8c7 100644 --- a/src/validate/validate_terrain.test.ts +++ b/src/validate/validate_terrain.test.ts @@ -4,7 +4,12 @@ import v8 from '../reference/v8.json' with {type: 'json'}; import {describe, test, expect} from 'vitest'; describe('Validate Terrain', () => { test('Should return error in case terrain is not an object', () => { - const errors = validateTerrain({validateSpec: validate, value: 1 as any, styleSpec: v8, style: {} as any}); + const errors = validateTerrain({ + validateSpec: validate, + value: 1 as any, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('number'); expect(errors[0].message).toContain('object'); @@ -12,7 +17,12 @@ describe('Validate Terrain', () => { }); test('Should return error in case terrain source is not a string', () => { - const errors = validateTerrain({validateSpec: validate, value: {source: 1 as any}, styleSpec: v8, style: {} as any}); + const errors = validateTerrain({ + validateSpec: validate, + value: {source: 1 as any}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('number'); expect(errors[0].message).toContain('string'); @@ -20,14 +30,24 @@ describe('Validate Terrain', () => { }); test('Should return error in case of unknown property', () => { - const errors = validateTerrain({validateSpec: validate, value: {a: 1} as any, styleSpec: v8, style: {} as any}); + const errors = validateTerrain({ + validateSpec: validate, + value: {a: 1} as any, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('a'); expect(errors[0].message).toContain('unknown'); }); test('Should return errors according to spec violations', () => { - const errors = validateTerrain({validateSpec: validate, value: {source: 1 as any, exaggeration: {} as any}, styleSpec: v8, style: {} as any}); + const errors = validateTerrain({ + validateSpec: validate, + value: {source: 1 as any, exaggeration: {} as any}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(2); expect(errors[0].message).toContain('number'); expect(errors[0].message).toContain('string'); @@ -38,7 +58,12 @@ describe('Validate Terrain', () => { }); test('Should pass if everything is according to spec', () => { - const errors = validateTerrain({validateSpec: validate, value: {source: 'source-id', exaggeration: 0.2}, styleSpec: v8, style: {} as any}); + const errors = validateTerrain({ + validateSpec: validate, + value: {source: 'source-id', exaggeration: 0.2}, + styleSpec: v8, + style: {} as any + }); expect(errors).toHaveLength(0); }); }); diff --git a/src/validate/validate_terrain.ts b/src/validate/validate_terrain.ts index c37552d0f..001a277f9 100644 --- a/src/validate/validate_terrain.ts +++ b/src/validate/validate_terrain.ts @@ -3,10 +3,12 @@ import {getType} from '../util/get_type'; import type {StyleSpecification, TerrainSpecification} from '../types.g'; import v8 from '../reference/v8.json' with {type: 'json'}; -export function validateTerrain( - options: {value: TerrainSpecification; styleSpec: typeof v8; style: StyleSpecification; validateSpec: Function} -): ValidationError[] { - +export function validateTerrain(options: { + value: TerrainSpecification; + styleSpec: typeof v8; + style: StyleSpecification; + validateSpec: Function; +}): ValidationError[] { const terrain = options.value; const styleSpec = options.styleSpec; const terrainSpec = styleSpec.terrain; @@ -18,22 +20,28 @@ export function validateTerrain( if (terrain === undefined) { return errors; } else if (rootType !== 'object') { - errors = errors.concat([new ValidationError('terrain', terrain, `object expected, ${rootType} found`)]); + errors = errors.concat([ + new ValidationError('terrain', terrain, `object expected, ${rootType} found`) + ]); return errors; } for (const key in terrain) { if (terrainSpec[key]) { - errors = errors.concat(options.validateSpec({ - key, - value: terrain[key], - valueSpec: terrainSpec[key], - validateSpec: options.validateSpec, - style, - styleSpec - })); + errors = errors.concat( + options.validateSpec({ + key, + value: terrain[key], + valueSpec: terrainSpec[key], + validateSpec: options.validateSpec, + style, + styleSpec + }) + ); } else { - errors = errors.concat([new ValidationError(key, terrain[key], `unknown property "${key}"`)]); + errors = errors.concat([ + new ValidationError(key, terrain[key], `unknown property "${key}"`) + ]); } } diff --git a/src/validate/validate_variable_anchor_offset_collection.test.ts b/src/validate/validate_variable_anchor_offset_collection.test.ts index 29ce68260..fcb590768 100644 --- a/src/validate/validate_variable_anchor_offset_collection.test.ts +++ b/src/validate/validate_variable_anchor_offset_collection.test.ts @@ -7,57 +7,91 @@ describe('Validate variableAnchorOffsetCollection', () => { const validateOpts = { validateSpec: validate, styleSpec: latest, - key: 'myProp', + key: 'myProp' }; test('Should return error if type is not array', () => { let errors = validateVariableAnchorOffsetCollection({...validateOpts, value: '3'}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); errors = validateVariableAnchorOffsetCollection({...validateOpts, value: 3}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); errors = validateVariableAnchorOffsetCollection({...validateOpts, value: true}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); errors = validateVariableAnchorOffsetCollection({...validateOpts, value: null}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); errors = validateVariableAnchorOffsetCollection({...validateOpts, value: {x: 1, y: 1}}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); }); test('Should return error if array has invalid length', () => { let errors = validateVariableAnchorOffsetCollection({...validateOpts, value: []}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); errors = validateVariableAnchorOffsetCollection({...validateOpts, value: [3]}); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); - - errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', [0, 0], 'bottom']}); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); + + errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', [0, 0], 'bottom'] + }); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp: variableAnchorOffsetCollection requires a non-empty array of even length'); + expect(errors[0].message).toBe( + 'myProp: variableAnchorOffsetCollection requires a non-empty array of even length' + ); }); test('Should return error if array contains invalid anchor', () => { - let errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['dennis', [0, 0]]}); + let errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['dennis', [0, 0]] + }); expect(errors).toHaveLength(1); - expect(errors[0].message).toBe('myProp[0]: expected one of [center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right], "dennis" found'); - - errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', [0, 0], 'dennis', [1, 1], 'not-dennis', [2, 2]]}); + expect(errors[0].message).toBe( + 'myProp[0]: expected one of [center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right], "dennis" found' + ); + + errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', [0, 0], 'dennis', [1, 1], 'not-dennis', [2, 2]] + }); expect(errors).toHaveLength(2); - expect(errors[0].message).toBe('myProp[2]: expected one of [center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right], "dennis" found'); - expect(errors[1].message).toBe('myProp[4]: expected one of [center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right], "not-dennis" found'); + expect(errors[0].message).toBe( + 'myProp[2]: expected one of [center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right], "dennis" found' + ); + expect(errors[1].message).toBe( + 'myProp[4]: expected one of [center, left, right, top, bottom, top-left, top-right, bottom-left, bottom-right], "not-dennis" found' + ); }); test('Should return error if array contains invalid offset', () => { - let errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', 'bottom']}); + let errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', 'bottom'] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('myProp[1]: array expected, string found'); @@ -73,24 +107,39 @@ describe('Validate variableAnchorOffsetCollection', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toBe('myProp[1]: array length 2 expected, length 0 found'); - errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', ['a', 'b']]}); + errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', ['a', 'b']] + }); expect(errors).toHaveLength(2); expect(errors[0].message).toBe('myProp[1][0]: number expected, string found'); expect(errors[1].message).toBe('myProp[1][1]: number expected, string found'); - errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', [0, NaN]]}); + errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', [0, NaN]] + }); expect(errors).toHaveLength(1); expect(errors[0].message).toBe('myProp[1][1]: number expected, NaN found'); }); test('Should pass if array alternates enum and point valies', () => { - let errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', [0, 0]]}); + let errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', [0, 0]] + }); expect(errors).toHaveLength(0); - errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', [0, 0], 'bottom', [1, 1]]}); + errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', [0, 0], 'bottom', [1, 1]] + }); expect(errors).toHaveLength(0); - errors = validateVariableAnchorOffsetCollection({...validateOpts, value: ['top', [0, 0], 'bottom', [1, 1], 'top-left', [2, 2], 'bottom', [3, 3]]}); + errors = validateVariableAnchorOffsetCollection({ + ...validateOpts, + value: ['top', [0, 0], 'bottom', [1, 1], 'top-left', [2, 2], 'bottom', [3, 3]] + }); expect(errors).toHaveLength(0); }); }); diff --git a/src/validate/validate_variable_anchor_offset_collection.ts b/src/validate/validate_variable_anchor_offset_collection.ts index cd26486f2..82a98ebae 100644 --- a/src/validate/validate_variable_anchor_offset_collection.ts +++ b/src/validate/validate_variable_anchor_offset_collection.ts @@ -10,31 +10,41 @@ export function validateVariableAnchorOffsetCollection(options): ValidationError const styleSpec = options.styleSpec; if (type !== 'array' || value.length < 1 || value.length % 2 !== 0) { - return [new ValidationError(key, value, 'variableAnchorOffsetCollection requires a non-empty array of even length')]; + return [ + new ValidationError( + key, + value, + 'variableAnchorOffsetCollection requires a non-empty array of even length' + ) + ]; } let errors = []; for (let i = 0; i < value.length; i += 2) { // Elements in even positions should be values from text-anchor enum - errors = errors.concat(validateEnum({ - key: `${key}[${i}]`, - value: value[i], - valueSpec: styleSpec['layout_symbol']['text-anchor'] - })); + errors = errors.concat( + validateEnum({ + key: `${key}[${i}]`, + value: value[i], + valueSpec: styleSpec['layout_symbol']['text-anchor'] + }) + ); // Elements in odd positions should be points (2-element numeric arrays) - errors = errors.concat(validateArray({ - key: `${key}[${i + 1}]`, - value: value[i + 1], - valueSpec: { - length: 2, - value: 'number' - }, - validateSpec: options.validateSpec, - style: options.style, - styleSpec - })); + errors = errors.concat( + validateArray({ + key: `${key}[${i + 1}]`, + value: value[i + 1], + valueSpec: { + length: 2, + value: 'number' + }, + validateSpec: options.validateSpec, + style: options.style, + styleSpec + }) + ); } return errors; diff --git a/src/validate_style.min.ts b/src/validate_style.min.ts index 905bfa9d9..41dfff0ce 100644 --- a/src/validate_style.min.ts +++ b/src/validate_style.min.ts @@ -1,4 +1,3 @@ - import {validateConstants} from './validate/validate_constants'; import {validate} from './validate/validate'; import {latest} from './reference/latest'; @@ -29,33 +28,39 @@ import type {StyleSpecification} from './types.g'; * const validate = require('@maplibre/maplibre-gl-style-spec/').validateStyleMin; * const errors = validate(style); */ -export function validateStyleMin(style: StyleSpecification, styleSpec = latest): Array { - +export function validateStyleMin( + style: StyleSpecification, + styleSpec = latest +): Array { let errors: ValidationError[] = []; - errors = errors.concat(validate({ - key: '', - value: style, - valueSpec: styleSpec.$root, - styleSpec, - style, - validateSpec: validate, - objectElementValidators: { - glyphs: validateGlyphsUrl, - '*'() { - return []; + errors = errors.concat( + validate({ + key: '', + value: style, + valueSpec: styleSpec.$root, + styleSpec, + style, + validateSpec: validate, + objectElementValidators: { + glyphs: validateGlyphsUrl, + '*'() { + return []; + } } - } - })); + }) + ); if (style['constants']) { - errors = errors.concat(validateConstants({ - key: 'constants', - value: style['constants'], - style, - styleSpec, - validateSpec: validate, - })); + errors = errors.concat( + validateConstants({ + key: 'constants', + value: style['constants'], + style, + styleSpec, + validateSpec: validate + }) + ); } return sortErrors(errors); @@ -74,7 +79,7 @@ validateStyleMin.paintProperty = wrapCleanErrors(injectValidateSpec(validatePain validateStyleMin.layoutProperty = wrapCleanErrors(injectValidateSpec(validateLayoutProperty)); function injectValidateSpec(validator: (options: object) => any) { - return function(options) { + return function (options) { return validator(Object.assign({}, options, {validateSpec: validate})); }; } @@ -86,7 +91,7 @@ function sortErrors(errors) { } function wrapCleanErrors(inner) { - return function(...args) { + return function (...args) { return sortErrors(inner.apply(this, args)); }; } diff --git a/src/validate_style.ts b/src/validate_style.ts index 6b2c7a9aa..f250b8602 100644 --- a/src/validate_style.ts +++ b/src/validate_style.ts @@ -1,4 +1,3 @@ - import {validateStyleMin} from './validate_style.min'; import {v8, ValidationError} from '.'; import {readStyle} from './read_style'; @@ -18,7 +17,10 @@ import type {StyleSpecification} from './types.g'; * const errors = validate(style); */ -export function validateStyle(style: StyleSpecification | string | Buffer, styleSpec = v8): Array { +export function validateStyle( + style: StyleSpecification | string | Buffer, + styleSpec = v8 +): Array { let s = style; try { diff --git a/src/visit.ts b/src/visit.ts index c47ce2ff6..6e23460df 100644 --- a/src/visit.ts +++ b/src/visit.ts @@ -35,17 +35,15 @@ export function eachLayer(style: StyleSpecification, callback: (_: LayerSpecific } } -type PropertyCallback = ( - a: { - path: [string, 'paint' | 'layout', string]; // [layerid, paint/layout, property key], - key: string; - value: PropertyValueSpecification | DataDrivenPropertyValueSpecification; - reference: StylePropertySpecification | null; - set: ( - a: PropertyValueSpecification | DataDrivenPropertyValueSpecification - ) => void; - } -) => void; +type PropertyCallback = (a: { + path: [string, 'paint' | 'layout', string]; // [layerid, paint/layout, property key], + key: string; + value: PropertyValueSpecification | DataDrivenPropertyValueSpecification; + reference: StylePropertySpecification | null; + set: ( + a: PropertyValueSpecification | DataDrivenPropertyValueSpecification + ) => void; +}) => void; export function eachProperty( style: StyleSpecification, diff --git a/test/build/style-spec.test.ts b/test/build/style-spec.test.ts index f17f945ac..90d275744 100644 --- a/test/build/style-spec.test.ts +++ b/test/build/style-spec.test.ts @@ -1,6 +1,6 @@ import {readdir} from 'fs/promises'; import {latest} from '../../src/reference/latest'; -import {latest as latestInBundle} from '../../dist/index'; +import {latest as latestInBundle} from '../../dist'; import fs from 'fs'; import {describe, test, expect} from 'vitest'; const minBundle = fs.readFileSync('dist/index.mjs', 'utf8'); @@ -29,8 +29,7 @@ describe('@maplibre/maplibre-gl-style-spec npm package', () => { expect(dirContents).toHaveLength(18); }); - test('exports components directly, not behind `default` - https://github.com/mapbox/mapbox-gl-js/issues/6601', async () => { - + test('exports components directly, not behind `default` - https://github.com/mapbox/mapbox-gl-js/issues/6601', async () => { expect(await import('../../dist/index.cjs')).toHaveProperty('validateStyleMin'); }); @@ -39,7 +38,7 @@ describe('@maplibre/maplibre-gl-style-spec npm package', () => { expect(minBundle.includes(latest.$root.version.doc)).toBeFalsy(); }); - test('"latest" entry point should be defined', async () => { + test('"latest" entry point should be defined', async () => { expect(latestInBundle).toBeDefined(); }); }); diff --git a/test/integration/expression/expression.test.ts b/test/integration/expression/expression.test.ts index cd9951d7f..892785c93 100644 --- a/test/integration/expression/expression.test.ts +++ b/test/integration/expression/expression.test.ts @@ -14,7 +14,7 @@ import { GlobalProperties, Feature, ZoomDependentExpression -} from '../../../src/index'; +} from '../../../src'; import {ExpressionParsingError} from '../../../src/expression/parsing_error'; import {getGeometry} from '../../lib/geometry'; import {deepEqual, stripPrecision} from '../../lib/json-diff'; @@ -47,8 +47,14 @@ type FixtureInput = [ properties?: Record; featureState?: Record; id?: any; - geometry?: GeoJSON.Point | GeoJSON.MultiPoint | GeoJSON.LineString | GeoJSON.MultiLineString | GeoJSON.Polygon | GeoJSON.MultiPolygon; - }, + geometry?: + | GeoJSON.Point + | GeoJSON.MultiPoint + | GeoJSON.LineString + | GeoJSON.MultiLineString + | GeoJSON.Polygon + | GeoJSON.MultiPolygon; + } ]; type FixtureResult = FixtureErrorResult | FixtureSuccessResult; @@ -82,10 +88,8 @@ type EvaluationSuccessOutput = any; const expressionTestFileNames = globSync('**/test.json', {cwd: __dirname}); describe('expression', () => { - for (const expressionTestFileName of expressionTestFileNames) { test(expressionTestFileName, () => { - const fixturePath = path.join(__dirname, expressionTestFileName); const fixture: ExpressionFixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8')); @@ -93,12 +97,12 @@ describe('expression', () => { const result = evaluateFixture(fixture, spec); if (process.env.UPDATE) { - fixture.expected = isFixtureErrorResult(result) ? - result : - { - compiled: result.compiled, - outputs: stripPrecision(result.outputs, DECIMAL_SIGNIFICANT_FIGURES), - }; + fixture.expected = isFixtureErrorResult(result) + ? result + : { + compiled: result.compiled, + outputs: stripPrecision(result.outputs, DECIMAL_SIGNIFICANT_FIGURES) + }; if (fixture.propertySpec) { fixture.propertySpec = spec; } @@ -109,22 +113,30 @@ describe('expression', () => { const expected = fixture.expected as FixtureResult; - const compileOk = deepEqual(result.compiled, expected.compiled, DECIMAL_SIGNIFICANT_FIGURES); + const compileOk = deepEqual( + result.compiled, + expected.compiled, + DECIMAL_SIGNIFICANT_FIGURES + ); try { expect(compileOk).toBeTruthy(); } catch { - throw new Error(`Compilation Failed:\nExpected ${JSON.stringify(expected.compiled)}\nResult ${JSON.stringify(result.compiled)}`); + throw new Error( + `Compilation Failed:\nExpected ${JSON.stringify(expected.compiled)}\nResult ${JSON.stringify(result.compiled)}` + ); } const resultOutputs = (result as any).outputs; const expectedOutputs = (expected as any).outputs; - const evalOk = compileOk && deepEqual(resultOutputs, expectedOutputs, DECIMAL_SIGNIFICANT_FIGURES); + const evalOk = + compileOk && deepEqual(resultOutputs, expectedOutputs, DECIMAL_SIGNIFICANT_FIGURES); try { expect(evalOk).toBeTruthy(); } catch { - throw new Error(`Evaluation Failed:\nExpected ${JSON.stringify(expectedOutputs)}\nResult ${JSON.stringify(resultOutputs)}`); + throw new Error( + `Evaluation Failed:\nExpected ${JSON.stringify(expectedOutputs)}\nResult ${JSON.stringify(resultOutputs)}` + ); } - }); } }); @@ -136,51 +148,66 @@ function getCompletePropertySpec(propertySpec: ExpressionFixture['propertySpec'] } if (!spec['expression']) { spec['expression'] = { - 'interpolated': true, - 'parameters': ['zoom', 'feature'], + interpolated: true, + parameters: ['zoom', 'feature'] }; } return spec as StylePropertySpecification; } -function evaluateFixture(fixture: ExpressionFixture, spec: StylePropertySpecification): FixtureResult { - const expression = isFunction(fixture.expression) ? - createPropertyExpression(convertFunction(fixture.expression, spec), spec, fixture.globalState) : - createPropertyExpression(fixture.expression, spec, fixture.globalState); +function evaluateFixture( + fixture: ExpressionFixture, + spec: StylePropertySpecification +): FixtureResult { + const expression = isFunction(fixture.expression) + ? createPropertyExpression( + convertFunction(fixture.expression, spec), + spec, + fixture.globalState + ) + : createPropertyExpression(fixture.expression, spec, fixture.globalState); if (expression.result === 'error') { return { - compiled: getCompilationErrorResult(expression.value), + compiled: getCompilationErrorResult(expression.value) }; } return { compiled: getCompilationSuccessResult(expression.value), - outputs: fixture.inputs === undefined ? [] : evaluateExpression(fixture.inputs, expression.value), + outputs: + fixture.inputs === undefined ? [] : evaluateExpression(fixture.inputs, expression.value) }; } -function getCompilationErrorResult(parsingErrors: ExpressionParsingError[]): CompilationErrorResult { +function getCompilationErrorResult( + parsingErrors: ExpressionParsingError[] +): CompilationErrorResult { return { result: 'error', errors: parsingErrors.map((err) => ({ key: err.key, - error: err.message, - })), + error: err.message + })) }; } -function getCompilationSuccessResult(expression: StylePropertyExpression): CompilationSuccessResult { +function getCompilationSuccessResult( + expression: StylePropertyExpression +): CompilationSuccessResult { const kind = expression.kind; const type = getStylePropertyExpressionType(expression); return { result: 'success', - isFeatureConstant: kind === 'constant' || kind ==='camera', + isFeatureConstant: kind === 'constant' || kind === 'camera', isZoomConstant: kind === 'constant' || kind === 'source', - type: toString(type), + type: toString(type) }; } -function evaluateExpression(inputs: FixtureInput[], expression: StylePropertyExpression): EvaluationOutput[] { +function evaluateExpression( + inputs: FixtureInput[], + expression: StylePropertyExpression +): EvaluationOutput[] { const type = getStylePropertyExpressionType(expression); const outputs: EvaluationOutput[] = []; @@ -190,7 +217,7 @@ function evaluateExpression(inputs: FixtureInput[], expression: StylePropertyExp const canonical = (canonicalID ?? null) as ICanonicalTileID | null; const feature: Partial> = { - properties: properties ?? {}, + properties: properties ?? {} }; if (id !== undefined) { feature.id = id; @@ -204,12 +231,14 @@ function evaluateExpression(inputs: FixtureInput[], expression: StylePropertyExp } try { - let value = (expression as ZoomConstantExpression | ZoomDependentExpression).evaluateWithoutErrorHandling( + let value = ( + expression as ZoomConstantExpression | ZoomDependentExpression + ).evaluateWithoutErrorHandling( input[0] as GlobalProperties, feature as Feature, featureState ?? {}, canonical as ICanonicalTileID, - availableImages ?? [], + availableImages ?? [] ); if (type.kind === 'color') { value = [value.r, value.g, value.b, value.a]; @@ -217,9 +246,7 @@ function evaluateExpression(inputs: FixtureInput[], expression: StylePropertyExp outputs.push(value); } catch (error) { outputs.push({ - error: error.name === 'ExpressionEvaluationError' ? - error.toJSON() : - error.message, + error: error.name === 'ExpressionEvaluationError' ? error.toJSON() : error.message }); } } diff --git a/test/integration/style-spec/validate_spec.test.ts b/test/integration/style-spec/validate_spec.test.ts index ace8d60d0..44a6e3297 100644 --- a/test/integration/style-spec/validate_spec.test.ts +++ b/test/integration/style-spec/validate_spec.test.ts @@ -19,7 +19,9 @@ describe('validate_spec', () => { }); test('errors from validate do not contain line numbers', () => { - const style = JSON.parse(fs.readFileSync('test/integration/style-spec/tests/bad-color.input.json', 'utf8')); + const style = JSON.parse( + fs.readFileSync('test/integration/style-spec/tests/bad-color.input.json', 'utf8') + ); const result = validate(style, latest); expect(result[0].line).toBeUndefined(); @@ -30,14 +32,12 @@ describe('Validate sdk-support in spec', () => { const issueTrackers = { js: 'https://github.com/maplibre/maplibre-gl-js/issues', android: 'https://github.com/maplibre/maplibre-native/issues', - ios: 'https://github.com/maplibre/maplibre-native/issues', + ios: 'https://github.com/maplibre/maplibre-native/issues' }; const platforms = Object.keys(issueTrackers); function validatePlatforms(platformSupport, path) { - const notSupportedOnAnyPlatform = platforms.every( - (platform) => !platformSupport[platform] - ); + const notSupportedOnAnyPlatform = platforms.every((platform) => !platformSupport[platform]); for (const platform of platforms) { test(`Validate sdk-support ${path.join('.')}`, () => { @@ -47,17 +47,20 @@ describe('Validate sdk-support in spec', () => { } if (!platformSupport[platform]) { - console.error(`Missing platform '${platform}' in sdk-support at ${path.join('.')}. Please create a tracking issue and add the link.`); + console.error( + `Missing platform '${platform}' in sdk-support at ${path.join('.')}. Please create a tracking issue and add the link.` + ); } expect(platformSupport[platform]).toBeTruthy(); - const maplibreIssue = - /https:\/\/github.com\/maplibre\/[^/]+\/issues\/(\d+)/; + const maplibreIssue = /https:\/\/github.com\/maplibre\/[^/]+\/issues\/(\d+)/; const version = /^\d+\.\d+\.\d+$/; const values = new Set(['supported', 'wontfix']); const support = platformSupport[platform]; - const supportValid = Boolean(support.match(maplibreIssue) || support.match(version) || values.has(support)); + const supportValid = Boolean( + support.match(maplibreIssue) || support.match(version) || values.has(support) + ); // Only the following values are supported: // - If supported: version number (e.g. 1.0.0) to indicate support since this version (or "supported" to indicate it has always been supported) // - If it will never be supported: "wontfix" to indicate it will never be supported @@ -72,9 +75,7 @@ describe('Validate sdk-support in spec', () => { * @param path - path in style spec where this object was found */ function validateSdkSupport(sdkSupportObj, path: string[]) { - Object.entries(sdkSupportObj).map(([key, obj]) => - validatePlatforms(obj, [...path, key]) - ); + Object.entries(sdkSupportObj).map(([key, obj]) => validatePlatforms(obj, [...path, key])); } /** diff --git a/test/lib/geometry.ts b/test/lib/geometry.ts index d66d7b063..b39caaa51 100644 --- a/test/lib/geometry.ts +++ b/test/lib/geometry.ts @@ -1,4 +1,3 @@ - import {ICanonicalTileID} from '../../src'; import {Point2D} from '../../src/point2d'; import {EXTENT, getTileCoordinates} from '../../src/util/geometry_util'; @@ -41,9 +40,17 @@ function convertLines(lines: GeoJSON.Position[][], canonical: ICanonicalTileID): return l; } -export function getGeometry(feature: {type?: any; id?: any; geometry?: Point2D[][]}, - geometry: GeoJSON.MultiLineString | GeoJSON.LineString | GeoJSON.Point | GeoJSON.MultiPoint | GeoJSON.Polygon | GeoJSON.MultiPolygon, - canonical: ICanonicalTileID) { +export function getGeometry( + feature: {type?: any; id?: any; geometry?: Point2D[][]}, + geometry: + | GeoJSON.MultiLineString + | GeoJSON.LineString + | GeoJSON.Point + | GeoJSON.MultiPoint + | GeoJSON.Polygon + | GeoJSON.MultiPolygon, + canonical: ICanonicalTileID +) { if (!geometry.coordinates) { return; } diff --git a/test/lib/json-diff.ts b/test/lib/json-diff.ts index f02015b05..22b2d6479 100644 --- a/test/lib/json-diff.ts +++ b/test/lib/json-diff.ts @@ -1,24 +1,20 @@ export function deepEqual(a, b, decimalSigFigs = 10): boolean { - if (typeof a !== typeof b) - return false; + if (typeof a !== typeof b) return false; if (typeof a === 'number') { return stripPrecision(a, decimalSigFigs) === stripPrecision(b, decimalSigFigs); } - if (a === null || typeof a !== 'object') - return a === b; + if (a === null || typeof a !== 'object') return a === b; const ka = Object.keys(a); const kb = Object.keys(b); - if (ka.length !== kb.length) - return false; + if (ka.length !== kb.length) return false; ka.sort(); kb.sort(); for (let i = 0; i < ka.length; i++) - if (ka[i] !== kb[i] || !deepEqual(a[ka[i]], b[ka[i]], decimalSigFigs)) - return false; + if (ka[i] !== kb[i] || !deepEqual(a[ka[i]], b[ka[i]], decimalSigFigs)) return false; return true; } @@ -27,11 +23,14 @@ export function stripPrecision(x, decimalSigFigs = 10) { // Intended for test output serialization: // strips down to 6 decimal sigfigs but stops at decimal point if (typeof x === 'number') { - if (x === 0) { return x; } + if (x === 0) { + return x; + } - const multiplier = Math.pow(10, - Math.max(0, - decimalSigFigs - Math.ceil(Math.log10(Math.abs(x))))); + const multiplier = Math.pow( + 10, + Math.max(0, decimalSigFigs - Math.ceil(Math.log10(Math.abs(x)))) + ); // We strip precision twice in a row here to avoid cases where // stripping an already stripped number will modify its value diff --git a/test/lib/util.ts b/test/lib/util.ts index bb00642e1..782ed2212 100644 --- a/test/lib/util.ts +++ b/test/lib/util.ts @@ -7,13 +7,16 @@ import {expect} from 'vitest'; * @param numDigits `expect.closeTo` numDigits parameter */ export function expectToMatchColor(toTest: Color, expectedSerialized: string, numDigits = 5) { - const [r, g, b, a] = expectedSerialized.match(/^rgb\(([\d.]+)% ([\d.]+)% ([\d.]+)% \/ ([\d.]+)\)$/).slice(1).map(Number); + const [r, g, b, a] = expectedSerialized + .match(/^rgb\(([\d.]+)% ([\d.]+)% ([\d.]+)% \/ ([\d.]+)\)$/) + .slice(1) + .map(Number); expect(toTest).toBeInstanceOf(Color); expect(toTest).toMatchObject({ - r: expect.closeTo(r / 100 * (a !== 0 ? a : 1), numDigits), - g: expect.closeTo(g / 100 * (a !== 0 ? a : 1), numDigits), - b: expect.closeTo(b / 100 * (a !== 0 ? a : 1), numDigits), - a: expect.closeTo(a, 4), + r: expect.closeTo((r / 100) * (a !== 0 ? a : 1), numDigits), + g: expect.closeTo((g / 100) * (a !== 0 ? a : 1), numDigits), + b: expect.closeTo((b / 100) * (a !== 0 ? a : 1), numDigits), + a: expect.closeTo(a, 4) }); } @@ -25,5 +28,5 @@ export function expectToMatchColor(toTest: Color, expectedSerialized: string, nu * @param numDigits `expect.closeTo` numDigits parameter */ export function expectCloseToArray(toTest: number[], expected: number[], numDigits = 5) { - expect(toTest).toEqual(expected.map(n => isNaN(n) ? n : expect.closeTo(n, numDigits))); + expect(toTest).toEqual(expected.map((n) => (isNaN(n) ? n : expect.closeTo(n, numDigits)))); } diff --git a/vitest.config.build.ts b/vitest.config.build.ts index 626734a02..b80560dae 100644 --- a/vitest.config.build.ts +++ b/vitest.config.build.ts @@ -4,16 +4,14 @@ export default defineConfig({ test: { name: 'build', environment: 'node', - include: [ - 'test/build/**/*.test.{ts,js}', - ], + include: ['test/build/**/*.test.{ts,js}'], coverage: { provider: 'v8', reporter: ['json', 'html'], exclude: ['node_modules/', 'dist/', '**/*.{test,test-d}.ts'], all: true, include: ['src'], - reportsDirectory: './coverage/vitest/build', - }, + reportsDirectory: './coverage/vitest/build' + } } -}); \ No newline at end of file +}); diff --git a/vitest.config.integration.ts b/vitest.config.integration.ts index 6e9bfb788..2d9842364 100644 --- a/vitest.config.integration.ts +++ b/vitest.config.integration.ts @@ -4,16 +4,14 @@ export default defineConfig({ test: { name: 'integration', environment: 'node', - include: [ - 'test/integration/**/*.test.{ts,js}', - ], + include: ['test/integration/**/*.test.{ts,js}'], coverage: { provider: 'v8', reporter: ['json', 'html'], exclude: ['node_modules/', 'dist/', '**/*.{test,test-d}.ts'], all: true, include: ['src'], - reportsDirectory: './coverage/vitest/integration', - }, - }, -}); \ No newline at end of file + reportsDirectory: './coverage/vitest/integration' + } + } +}); diff --git a/vitest.config.unit.ts b/vitest.config.unit.ts index 0b5b90dcf..2b28a8c9e 100644 --- a/vitest.config.unit.ts +++ b/vitest.config.unit.ts @@ -6,20 +6,16 @@ export default defineConfig({ environment: 'node', typecheck: { enabled: true, - include: [ - 'src/**/*.test-d.ts', - ], + include: ['src/**/*.test-d.ts'] }, - include: [ - 'src/**/*.test.{ts,js}' - ], + include: ['src/**/*.test.{ts,js}'], coverage: { provider: 'v8', reporter: ['json', 'html'], exclude: ['node_modules/', 'dist/', '**/*.{test,test-d}.ts'], all: true, include: ['src'], - reportsDirectory: './coverage/vitest/unit', - }, - }, -}); \ No newline at end of file + reportsDirectory: './coverage/vitest/unit' + } + } +});