forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
f0823f4c4d
524 changed files with 18747 additions and 8546 deletions
|
@ -2,7 +2,6 @@ import { LayerConfigJson } from "../Json/LayerConfigJson"
|
|||
import { Utils } from "../../../Utils"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import { ConversionContext } from "./ConversionContext"
|
||||
import { T } from "vitest/dist/types-aac763a5"
|
||||
|
||||
export interface DesugaringContext {
|
||||
tagRenderings: Map<string, QuestionableTagRenderingConfigJson>
|
||||
|
@ -11,10 +10,11 @@ export interface DesugaringContext {
|
|||
}
|
||||
|
||||
export type ConversionMsgLevel = "debug" | "information" | "warning" | "error"
|
||||
|
||||
export interface ConversionMessage {
|
||||
context: ConversionContext
|
||||
message: string
|
||||
level: ConversionMsgLevel
|
||||
readonly context: ConversionContext
|
||||
readonly message: string
|
||||
readonly level: ConversionMsgLevel
|
||||
}
|
||||
|
||||
export abstract class Conversion<TIn, TOut> {
|
||||
|
@ -85,6 +85,7 @@ export class Pure<TIn, TOut> extends Conversion<TIn, TOut> {
|
|||
export class Bypass<T> extends DesugaringStep<T> {
|
||||
private readonly _applyIf: (t: T) => boolean
|
||||
private readonly _step: DesugaringStep<T>
|
||||
|
||||
constructor(applyIf: (t: T) => boolean, step: DesugaringStep<T>) {
|
||||
super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass")
|
||||
this._applyIf = applyIf
|
||||
|
@ -102,7 +103,6 @@ export class Bypass<T> extends DesugaringStep<T> {
|
|||
export class Each<X, Y> extends Conversion<X[], Y[]> {
|
||||
private readonly _step: Conversion<X, Y>
|
||||
private readonly _msg: string
|
||||
private readonly _filter: (x: X) => boolean
|
||||
|
||||
constructor(step: Conversion<X, Y>, options?: { msg?: string }) {
|
||||
super(
|
||||
|
@ -173,7 +173,7 @@ export class Pass<T> extends Conversion<T, T> {
|
|||
super(message ?? "Does nothing, often to swap out steps in testing", [], "Pass")
|
||||
}
|
||||
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
convert(json: T, _: ConversionContext): T {
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
@ -224,6 +224,7 @@ export class FirstOf<T, X> extends Conversion<T, X> {
|
|||
export class Cached<TIn, TOut> extends Conversion<TIn, TOut> {
|
||||
private _step: Conversion<TIn, TOut>
|
||||
private readonly key: string
|
||||
|
||||
constructor(step: Conversion<TIn, TOut>) {
|
||||
super("Secretly caches the output for the given input", [], "cached")
|
||||
this._step = step
|
||||
|
@ -242,9 +243,11 @@ export class Cached<TIn, TOut> extends Conversion<TIn, TOut> {
|
|||
return converted
|
||||
}
|
||||
}
|
||||
|
||||
export class Fuse<T> extends DesugaringStep<T> {
|
||||
private readonly steps: DesugaringStep<T>[]
|
||||
protected debug = false
|
||||
private readonly steps: DesugaringStep<T>[]
|
||||
|
||||
constructor(doc: string, ...steps: DesugaringStep<T>[]) {
|
||||
super(
|
||||
(doc ?? "") +
|
||||
|
@ -301,7 +304,7 @@ export class SetDefault<T> extends DesugaringStep<T> {
|
|||
this._overrideEmptyString = overrideEmptyString
|
||||
}
|
||||
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
convert(json: T, _: ConversionContext): T {
|
||||
if (json === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ConversionMessage, ConversionMsgLevel } from "./Conversion"
|
||||
|
||||
export class ConversionContext {
|
||||
private static reported = false
|
||||
/**
|
||||
* The path within the data structure where we are currently operating
|
||||
*/
|
||||
|
@ -10,7 +11,6 @@ export class ConversionContext {
|
|||
*/
|
||||
readonly operation: ReadonlyArray<string>
|
||||
readonly messages: ConversionMessage[]
|
||||
|
||||
private _hasErrors: boolean = false
|
||||
|
||||
private constructor(
|
||||
|
@ -32,7 +32,6 @@ export class ConversionContext {
|
|||
}
|
||||
}
|
||||
}
|
||||
private static reported = false
|
||||
|
||||
public static construct(path: (string | number)[], operation: string[]) {
|
||||
return new ConversionContext([], [...path], [...operation])
|
||||
|
@ -76,6 +75,31 @@ export class ConversionContext {
|
|||
return "\x1b[31m" + s + "\x1b[0m"
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an inline edit of the messages for which a new path is defined
|
||||
* This is a slight hack
|
||||
* @param rewritePath
|
||||
*/
|
||||
public rewriteMessages(
|
||||
rewritePath: (
|
||||
p: ReadonlyArray<number | string>
|
||||
) => undefined | ReadonlyArray<number | string>
|
||||
): void {
|
||||
for (let i = 0; i < this.messages.length; i++) {
|
||||
const m = this.messages[i]
|
||||
const newPath = rewritePath(m.context.path)
|
||||
if (!newPath) {
|
||||
continue
|
||||
}
|
||||
const rewrittenContext = new ConversionContext(
|
||||
this.messages,
|
||||
newPath,
|
||||
m.context.operation
|
||||
)
|
||||
this.messages[i] = <ConversionMessage>{ ...m, context: rewrittenContext }
|
||||
}
|
||||
}
|
||||
|
||||
public enter(key: string | number | (string | number)[]) {
|
||||
if (!Array.isArray(key)) {
|
||||
if (typeof key === "number" && key < 0) {
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
|||
this._includeClosedNotesDays = includeClosedNotesDays
|
||||
}
|
||||
|
||||
convert(layerJson: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
convert(layerJson: LayerConfigJson, _: ConversionContext): LayerConfigJson {
|
||||
const t = Translations.t.importLayer
|
||||
|
||||
/**
|
||||
|
|
|
@ -589,7 +589,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
|||
this._desugaring = desugaring
|
||||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
convert(json: LayerConfigJson, _: ConversionContext): LayerConfigJson {
|
||||
if (this._desugaring.tagRenderings === null) {
|
||||
return json
|
||||
}
|
||||
|
@ -762,7 +762,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
|||
* }
|
||||
* }}
|
||||
* const context = ConversionContext.test()
|
||||
* RewriteSpecial.convertIfNeeded(special, context) // => {"en": "<h3>Entrances</h3>This building has {_entrances_count} entrances:{multi(_entrance_properties_with_width,An <a href='#&LBRACEid&RBRACE'>entrance</a> of &LBRACEcanonical&LPARENSwidth&RPARENS&RBRACE)}{_entrances_count_without_width_count} entrances don't have width information yet"}
|
||||
* RewriteSpecial.convertIfNeeded(special, context) // => {"en": "<h3>Entrances</h3>This building has {_entrances_count} entrances:{multi(_entrance_properties_with_width,An <a href='#&LBRACEid&RBRACE'>entrance</a> of &LBRACEcanonical&LPARENSwidth&RPARENS&RBRACE,)}{_entrances_count_without_width_count} entrances don't have width information yet"}
|
||||
* context.getAll("error") // => []
|
||||
*
|
||||
* // another actual test
|
||||
|
@ -773,7 +773,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
|||
* "tagrendering": "<b>{id}</b> ({distance}m) {tagApply(a,b,c)}"
|
||||
* }}
|
||||
* const context = ConversionContext.test()
|
||||
* RewriteSpecial.convertIfNeeded(special, context) // => {"*": "{multi(_nearby_bicycle_parkings:props,<b>&LBRACEid&RBRACE</b> &LPARENS&LBRACEdistance&RBRACEm&RPARENS &LBRACEtagApply&LPARENSa&COMMAb&COMMAc&RPARENS&RBRACE)}"}
|
||||
* RewriteSpecial.convertIfNeeded(special, context) // => {"*": "{multi(_nearby_bicycle_parkings:props,<b>&LBRACEid&RBRACE</b> &LPARENS&LBRACEdistance&RBRACEm&RPARENS &LBRACEtagApply&LPARENSa&COMMAb&COMMAc&RPARENS&RBRACE,)}"}
|
||||
* context.getAll("error") // => []
|
||||
*/
|
||||
private static convertIfNeeded(
|
||||
|
@ -1039,7 +1039,7 @@ class SetFullNodeDatabase extends DesugaringStep<LayerConfigJson> {
|
|||
if (!needsSpecial) {
|
||||
return json
|
||||
}
|
||||
context.info("Layer " + json.id + " needs the fullNodeDatabase")
|
||||
context.debug("Layer " + json.id + " needs the fullNodeDatabase")
|
||||
return { ...json, fullNodeDatabase: true }
|
||||
}
|
||||
}
|
||||
|
@ -1088,7 +1088,7 @@ class AddFavouriteBadges extends DesugaringStep<LayerConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
convert(json: LayerConfigJson, _: ConversionContext): LayerConfigJson {
|
||||
if (json.source === "special" || json.source === "special:library") {
|
||||
return json
|
||||
}
|
||||
|
@ -1113,7 +1113,7 @@ export class AddRatingBadge extends DesugaringStep<LayerConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
convert(json: LayerConfigJson, _: ConversionContext): LayerConfigJson {
|
||||
if (!json.tagRenderings) {
|
||||
return json
|
||||
}
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import { Concat, Conversion, DesugaringContext, DesugaringStep, Each, Fuse, On, Pass, SetDefault } from "./Conversion"
|
||||
import {
|
||||
Concat,
|
||||
Conversion,
|
||||
DesugaringContext,
|
||||
DesugaringStep,
|
||||
Each,
|
||||
Fuse,
|
||||
On,
|
||||
Pass,
|
||||
SetDefault,
|
||||
} from "./Conversion"
|
||||
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
|
||||
import { PrepareLayer } from "./PrepareLayer"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
|
@ -19,7 +29,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
super(
|
||||
"Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form. Note that 'tagRenderings+' will be inserted before 'leftover-questions'",
|
||||
[],
|
||||
"SubstituteLayer",
|
||||
"SubstituteLayer"
|
||||
)
|
||||
this._state = state
|
||||
}
|
||||
|
@ -61,6 +71,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
|
||||
for (const name of names) {
|
||||
const found = Utils.Clone(state.sharedLayers.get(name))
|
||||
found["_basedOn"] = name
|
||||
if (found === undefined) {
|
||||
reportNotFound(name)
|
||||
continue
|
||||
|
@ -70,15 +81,16 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
(found["tagRenderings"] ?? []).length > 0
|
||||
) {
|
||||
context.err(
|
||||
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`,
|
||||
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
|
||||
)
|
||||
}
|
||||
try {
|
||||
|
||||
const trPlus = json["override"]["tagRenderings+"]
|
||||
if(trPlus){
|
||||
let index = found.tagRenderings.findIndex(tr => tr["id"] === "leftover-questions")
|
||||
if(index < 0){
|
||||
if (trPlus) {
|
||||
let index = found.tagRenderings.findIndex(
|
||||
(tr) => tr["id"] === "leftover-questions"
|
||||
)
|
||||
if (index < 0) {
|
||||
index = found.tagRenderings.length
|
||||
}
|
||||
found.tagRenderings.splice(index, 0, ...trPlus)
|
||||
|
@ -90,14 +102,18 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
} catch (e) {
|
||||
context.err(
|
||||
`Could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
|
||||
json["override"],
|
||||
)}`,
|
||||
json["override"]
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
if (json["hideTagRenderingsWithLabels"]) {
|
||||
if (typeof json["hideTagRenderingsWithLabels"] === "string") {
|
||||
throw "At " + context + ".hideTagRenderingsWithLabels should be a list containing strings, you specified a string"
|
||||
throw (
|
||||
"At " +
|
||||
context +
|
||||
".hideTagRenderingsWithLabels should be a list containing strings, you specified a string"
|
||||
)
|
||||
}
|
||||
const hideLabels: Set<string> = new Set(json["hideTagRenderingsWithLabels"])
|
||||
// These labels caused at least one deletion
|
||||
|
@ -111,9 +127,9 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
usedLabels.add(labels[forbiddenLabel])
|
||||
context.info(
|
||||
"Dropping tagRendering " +
|
||||
tr["id"] +
|
||||
" as it has a forbidden label: " +
|
||||
labels[forbiddenLabel],
|
||||
tr["id"] +
|
||||
" as it has a forbidden label: " +
|
||||
labels[forbiddenLabel]
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
@ -122,7 +138,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
if (hideLabels.has(tr["id"])) {
|
||||
usedLabels.add(tr["id"])
|
||||
context.info(
|
||||
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label",
|
||||
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label"
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
@ -131,10 +147,10 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
usedLabels.add(tr["group"])
|
||||
context.info(
|
||||
"Dropping tagRendering " +
|
||||
tr["id"] +
|
||||
" as its group `" +
|
||||
tr["group"] +
|
||||
"` is a forbidden label",
|
||||
tr["id"] +
|
||||
" as its group `" +
|
||||
tr["group"] +
|
||||
"` is a forbidden label"
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
@ -145,8 +161,8 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
if (unused.length > 0) {
|
||||
context.err(
|
||||
"This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
|
||||
unused.join(", ") +
|
||||
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore",
|
||||
unused.join(", ") +
|
||||
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
|
||||
)
|
||||
}
|
||||
found.tagRenderings = filtered
|
||||
|
@ -163,7 +179,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
super(
|
||||
"Adds the default layers, namely: " + Constants.added_by_default.join(", "),
|
||||
["layers"],
|
||||
"AddDefaultLayers",
|
||||
"AddDefaultLayers"
|
||||
)
|
||||
this._state = state
|
||||
}
|
||||
|
@ -186,10 +202,10 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
if (alreadyLoaded.has(v.id)) {
|
||||
context.warn(
|
||||
"Layout " +
|
||||
context +
|
||||
" already has a layer with name " +
|
||||
v.id +
|
||||
"; skipping inclusion of this builtin layer",
|
||||
context +
|
||||
" already has a layer with name " +
|
||||
v.id +
|
||||
"; skipping inclusion of this builtin layer"
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
@ -205,14 +221,14 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
super(
|
||||
"For every layer in the 'layers'-list, create a new layer which'll import notes. (Note that priviliged layers and layers which have a geojson-source set are ignored)",
|
||||
["layers"],
|
||||
"AddImportLayers",
|
||||
"AddImportLayers"
|
||||
)
|
||||
}
|
||||
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
if (!(json.enableNoteImports ?? true)) {
|
||||
context.info(
|
||||
"Not creating a note import layers for theme " + json.id + " as they are disabled",
|
||||
"Not creating a note import layers for theme " + json.id + " as they are disabled"
|
||||
)
|
||||
return json
|
||||
}
|
||||
|
@ -247,7 +263,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
try {
|
||||
const importLayerResult = creator.convert(
|
||||
layer,
|
||||
context.inOperation(this.name).enter(i1),
|
||||
context.inOperation(this.name).enter(i1)
|
||||
)
|
||||
if (importLayerResult !== undefined) {
|
||||
json.layers.push(importLayerResult)
|
||||
|
@ -266,7 +282,7 @@ class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson>
|
|||
super(
|
||||
"Adds context to translations, including the prefix 'themes:json.id'; this is to make sure terms in an 'overrides' or inline layer are linkable too",
|
||||
["_context"],
|
||||
"AddContextToTranlationsInLayout",
|
||||
"AddContextToTranlationsInLayout"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -281,11 +297,11 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
|
|||
super(
|
||||
"Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards",
|
||||
["overrideAll", "layers"],
|
||||
"ApplyOverrideAll",
|
||||
"ApplyOverrideAll"
|
||||
)
|
||||
}
|
||||
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
convert(json: LayoutConfigJson, _: ConversionContext): LayoutConfigJson {
|
||||
const overrideAll = json.overrideAll
|
||||
if (overrideAll === undefined) {
|
||||
return json
|
||||
|
@ -309,8 +325,9 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
|
|||
if (!layer.tagRenderings) {
|
||||
layer.tagRenderings = tagRenderingsPlus
|
||||
} else {
|
||||
|
||||
let index = layer.tagRenderings.findIndex(tr => tr["id"] === "leftover-questions")
|
||||
let index = layer.tagRenderings.findIndex(
|
||||
(tr) => tr["id"] === "leftover-questions"
|
||||
)
|
||||
if (index < 0) {
|
||||
index = layer.tagRenderings.length - 1
|
||||
}
|
||||
|
@ -337,7 +354,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
Some layers (e.g. \`all_buildings_and_walls\' or \'streets_with_a_name\') are invisible, so by default, \'force_load\' is set too.
|
||||
`,
|
||||
["layers"],
|
||||
"AddDependencyLayersToTheme",
|
||||
"AddDependencyLayersToTheme"
|
||||
)
|
||||
this._state = state
|
||||
}
|
||||
|
@ -345,7 +362,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
private static CalculateDependencies(
|
||||
alreadyLoaded: LayerConfigJson[],
|
||||
allKnownLayers: Map<string, LayerConfigJson>,
|
||||
themeId: string,
|
||||
themeId: string
|
||||
): { config: LayerConfigJson; reason: string }[] {
|
||||
const dependenciesToAdd: { config: LayerConfigJson; reason: string }[] = []
|
||||
const loadedLayerIds: Set<string> = new Set<string>(alreadyLoaded.map((l) => l.id))
|
||||
|
@ -368,7 +385,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
for (const layerConfig of alreadyLoaded) {
|
||||
try {
|
||||
const layerDeps = DependencyCalculator.getLayerDependencies(
|
||||
new LayerConfig(layerConfig, themeId + "(dependencies)"),
|
||||
new LayerConfig(layerConfig, themeId + "(dependencies)")
|
||||
)
|
||||
dependencies.push(...layerDeps)
|
||||
} catch (e) {
|
||||
|
@ -405,10 +422,10 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
if (dep === undefined) {
|
||||
const message = [
|
||||
"Loading a dependency failed: layer " +
|
||||
unmetDependency.neededLayer +
|
||||
" is not found, neither as layer of " +
|
||||
themeId +
|
||||
" nor as builtin layer.",
|
||||
unmetDependency.neededLayer +
|
||||
" is not found, neither as layer of " +
|
||||
themeId +
|
||||
" nor as builtin layer.",
|
||||
reason,
|
||||
"Loaded layers are: " + alreadyLoaded.map((l) => l.id).join(","),
|
||||
]
|
||||
|
@ -424,7 +441,7 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
})
|
||||
loadedLayerIds.add(dep.id)
|
||||
unmetDependencies = unmetDependencies.filter(
|
||||
(d) => d.neededLayer !== unmetDependency.neededLayer,
|
||||
(d) => d.neededLayer !== unmetDependency.neededLayer
|
||||
)
|
||||
}
|
||||
} while (unmetDependencies.length > 0)
|
||||
|
@ -445,14 +462,12 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
const dependencies = AddDependencyLayersToTheme.CalculateDependencies(
|
||||
layers,
|
||||
allKnownLayers,
|
||||
theme.id,
|
||||
theme.id
|
||||
)
|
||||
for (const dependency of dependencies) {
|
||||
}
|
||||
if (dependencies.length > 0) {
|
||||
for (const dependency of dependencies) {
|
||||
context.info(
|
||||
"Added " + dependency.config.id + " to the theme. " + dependency.reason,
|
||||
"Added " + dependency.config.id + " to the theme. " + dependency.reason
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +509,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
|||
super(
|
||||
"Generates a warning if a theme uses an unsubstituted layer",
|
||||
["layers"],
|
||||
"WarnForUnsubstitutedLayersInTheme",
|
||||
"WarnForUnsubstitutedLayersInTheme"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -506,7 +521,7 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
|||
context
|
||||
.enter("layers")
|
||||
.err(
|
||||
"No layers are defined. You must define at least one layer to have a valid theme",
|
||||
"No layers are defined. You must define at least one layer to have a valid theme"
|
||||
)
|
||||
return json
|
||||
}
|
||||
|
@ -530,16 +545,64 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
|||
|
||||
context.warn(
|
||||
"The theme " +
|
||||
json.id +
|
||||
" has an inline layer: " +
|
||||
layer["id"] +
|
||||
". This is discouraged.",
|
||||
json.id +
|
||||
" has an inline layer: " +
|
||||
layer["id"] +
|
||||
". This is discouraged."
|
||||
)
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
class PostvalidateTheme extends DesugaringStep<LayoutConfigJson> {
|
||||
private readonly _state: DesugaringContext
|
||||
constructor(state: DesugaringContext) {
|
||||
super("Various validation steps when everything is done", [], "PostvalidateTheme")
|
||||
this._state = state
|
||||
}
|
||||
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
for (const l of json.layers) {
|
||||
const layer = <LayerConfigJson>l
|
||||
const basedOn = <string>layer["_basedOn"]
|
||||
const basedOnDef = this._state.sharedLayers.get(basedOn)
|
||||
if (!basedOn) {
|
||||
continue
|
||||
}
|
||||
if (layer["name"] === null) {
|
||||
continue
|
||||
}
|
||||
const sameBasedOn = <LayerConfigJson[]>(
|
||||
json.layers.filter(
|
||||
(l) => l["_basedOn"] === layer["_basedOn"] && l["id"] !== layer.id
|
||||
)
|
||||
)
|
||||
const minZoomAll = Math.min(...sameBasedOn.map((sbo) => sbo.minzoom))
|
||||
|
||||
const sameNameDetected = sameBasedOn.some(
|
||||
(same) => JSON.stringify(layer["name"]) === JSON.stringify(same["name"])
|
||||
)
|
||||
if (!sameNameDetected) {
|
||||
// The name is unique, so it'll won't be confusing
|
||||
continue
|
||||
}
|
||||
if (minZoomAll < layer.minzoom) {
|
||||
context.err(
|
||||
"There are multiple layers based on " +
|
||||
basedOn +
|
||||
". The layer with id " +
|
||||
layer.id +
|
||||
" has a minzoom of " +
|
||||
layer.minzoom +
|
||||
", and has a name set. Another similar layer has a lower minzoom. As such, the layer selection might show 'zoom in to see features' even though some of the features are already visible. Set `\"name\": null` for this layer and eventually remove the 'name':null for the other layer."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
}
|
||||
export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
||||
private state: DesugaringContext
|
||||
|
||||
|
@ -547,7 +610,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
|||
state: DesugaringContext,
|
||||
options?: {
|
||||
skipDefaultLayers: false | boolean
|
||||
},
|
||||
}
|
||||
) {
|
||||
super(
|
||||
"Fully prepares and expands a theme",
|
||||
|
@ -559,7 +622,8 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
|||
new SetDefault("socialImage", "assets/SocialImage.png", true),
|
||||
// We expand all tagrenderings first...
|
||||
new On("layers", new Each(new PrepareLayer(state))),
|
||||
// Then we apply the override all. Note that it'll cheat with tagRenderings+
|
||||
// Then we apply the override all. We must first expand everything in case that we override something in an expanded tag
|
||||
// Note that it'll cheat with tagRenderings+
|
||||
new ApplyOverrideAll(),
|
||||
// And then we prepare all the layers _again_ in case that an override all contained unexpanded tagrenderings!
|
||||
new On("layers", new Each(new PrepareLayer(state))),
|
||||
|
@ -568,6 +632,7 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
|||
: new AddDefaultLayers(state),
|
||||
new AddDependencyLayersToTheme(state),
|
||||
new AddImportLayers(),
|
||||
new PostvalidateTheme(state)
|
||||
)
|
||||
this.state = state
|
||||
}
|
||||
|
@ -582,13 +647,13 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
|||
const needsNodeDatabase = result.layers?.some((l: LayerConfigJson) =>
|
||||
l.tagRenderings?.some((tr) =>
|
||||
ValidationUtils.getSpecialVisualisations(<any>tr)?.some(
|
||||
(special) => special.needsNodeDatabase,
|
||||
),
|
||||
),
|
||||
(special) => special.needsNodeDatabase
|
||||
)
|
||||
)
|
||||
)
|
||||
if (needsNodeDatabase) {
|
||||
context.info(
|
||||
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes",
|
||||
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
|
||||
)
|
||||
result.enableNodeDatabase = true
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@ import { And } from "../../../Logic/Tags/And"
|
|||
import Translations from "../../../UI/i18n/Translations"
|
||||
import FilterConfigJson from "../Json/FilterConfigJson"
|
||||
import DeleteConfig from "../DeleteConfig"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import Validators from "../../../UI/InputElement/Validators"
|
||||
import TagRenderingConfig from "../TagRenderingConfig"
|
||||
import { parse as parse_html } from "node-html-parser"
|
||||
|
@ -31,7 +34,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
|
|||
super(
|
||||
"Checks that the given object is fully translated in the specified languages",
|
||||
[],
|
||||
"ValidateLanguageCompleteness",
|
||||
"ValidateLanguageCompleteness"
|
||||
)
|
||||
this._languages = languages ?? ["en"]
|
||||
}
|
||||
|
@ -45,18 +48,18 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
|
|||
.filter(
|
||||
(t) =>
|
||||
t.tr.translations[neededLanguage] === undefined &&
|
||||
t.tr.translations["*"] === undefined,
|
||||
t.tr.translations["*"] === undefined
|
||||
)
|
||||
.forEach((missing) => {
|
||||
context
|
||||
.enter(missing.context.split("."))
|
||||
.err(
|
||||
`The theme ${obj.id} should be translation-complete for ` +
|
||||
neededLanguage +
|
||||
", but it lacks a translation for " +
|
||||
missing.context +
|
||||
".\n\tThe known translation is " +
|
||||
missing.tr.textFor("en"),
|
||||
neededLanguage +
|
||||
", but it lacks a translation for " +
|
||||
missing.context +
|
||||
".\n\tThe known translation is " +
|
||||
missing.tr.textFor("en")
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -73,7 +76,7 @@ export class DoesImageExist extends DesugaringStep<string> {
|
|||
constructor(
|
||||
knownImagePaths: Set<string>,
|
||||
checkExistsSync: (path: string) => boolean = undefined,
|
||||
ignore?: Set<string>,
|
||||
ignore?: Set<string>
|
||||
) {
|
||||
super("Checks if an image exists", [], "DoesImageExist")
|
||||
this._ignore = ignore
|
||||
|
@ -109,15 +112,15 @@ export class DoesImageExist extends DesugaringStep<string> {
|
|||
if (!this._knownImagePaths.has(image)) {
|
||||
if (this.doesPathExist === undefined) {
|
||||
context.err(
|
||||
`Image with path ${image} not found or not attributed; it is used in ${context}`,
|
||||
`Image with path ${image} not found or not attributed; it is used in ${context}`
|
||||
)
|
||||
} else if (!this.doesPathExist(image)) {
|
||||
context.err(
|
||||
`Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.`,
|
||||
`Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.`
|
||||
)
|
||||
} else {
|
||||
context.err(
|
||||
`Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`,
|
||||
`Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +144,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
doesImageExist: DoesImageExist,
|
||||
path: string,
|
||||
isBuiltin: boolean,
|
||||
sharedTagRenderings?: Set<string>,
|
||||
sharedTagRenderings?: Set<string>
|
||||
) {
|
||||
super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme")
|
||||
this._validateImage = doesImageExist
|
||||
|
@ -160,15 +163,15 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
if (json["units"] !== undefined) {
|
||||
context.err(
|
||||
"The theme " +
|
||||
json.id +
|
||||
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ",
|
||||
json.id +
|
||||
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
|
||||
)
|
||||
}
|
||||
if (json["roamingRenderings"] !== undefined) {
|
||||
context.err(
|
||||
"Theme " +
|
||||
json.id +
|
||||
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead",
|
||||
json.id +
|
||||
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -186,10 +189,10 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
for (const remoteImage of remoteImages) {
|
||||
context.err(
|
||||
"Found a remote image: " +
|
||||
remoteImage.path +
|
||||
" in theme " +
|
||||
json.id +
|
||||
", please download it.",
|
||||
remoteImage.path +
|
||||
" in theme " +
|
||||
json.id +
|
||||
", please download it."
|
||||
)
|
||||
}
|
||||
for (const image of images) {
|
||||
|
@ -205,17 +208,17 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
|
||||
const filename = this._path.substring(
|
||||
this._path.lastIndexOf("/") + 1,
|
||||
this._path.length - 5,
|
||||
this._path.length - 5
|
||||
)
|
||||
if (theme.id !== filename) {
|
||||
context.err(
|
||||
"Theme ids should be the same as the name.json, but we got id: " +
|
||||
theme.id +
|
||||
" and filename " +
|
||||
filename +
|
||||
" (" +
|
||||
this._path +
|
||||
")",
|
||||
theme.id +
|
||||
" and filename " +
|
||||
filename +
|
||||
" (" +
|
||||
this._path +
|
||||
")"
|
||||
)
|
||||
}
|
||||
this._validateImage.convert(theme.icon, context.enter("icon"))
|
||||
|
@ -223,13 +226,13 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"]))
|
||||
if (dups.length > 0) {
|
||||
context.err(
|
||||
`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`,
|
||||
`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`
|
||||
)
|
||||
}
|
||||
if (json["mustHaveLanguage"] !== undefined) {
|
||||
new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert(
|
||||
theme,
|
||||
context,
|
||||
context
|
||||
)
|
||||
}
|
||||
if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) {
|
||||
|
@ -237,7 +240,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
const targetLanguage = theme.title.SupportedLanguages()[0]
|
||||
if (targetLanguage !== "en") {
|
||||
context.err(
|
||||
`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`,
|
||||
`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -283,7 +286,9 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
for (let i = 0; i < theme.layers.length; i++) {
|
||||
const layer = theme.layers[i]
|
||||
if (!layer.id.match("[a-z][a-z0-9_]*")) {
|
||||
context.enters("layers", i, "id").err("Invalid ID:" + layer.id + "should match [a-z][a-z0-9_]*")
|
||||
context
|
||||
.enters("layers", i, "id")
|
||||
.err("Invalid ID:" + layer.id + "should match [a-z][a-z0-9_]*")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,7 +301,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
|
|||
doesImageExist: DoesImageExist,
|
||||
path: string,
|
||||
isBuiltin: boolean,
|
||||
sharedTagRenderings?: Set<string>,
|
||||
sharedTagRenderings?: Set<string>
|
||||
) {
|
||||
super(
|
||||
"Validates a theme and the contained layers",
|
||||
|
@ -306,10 +311,10 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
|
|||
new Each(
|
||||
new Bypass(
|
||||
(layer) => Constants.added_by_default.indexOf(<any>layer.id) < 0,
|
||||
new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true),
|
||||
),
|
||||
),
|
||||
),
|
||||
new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +324,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
|
|||
super(
|
||||
"Checks that an 'overrideAll' does not override a single override",
|
||||
[],
|
||||
"OverrideShadowingCheck",
|
||||
"OverrideShadowingCheck"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -380,7 +385,9 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> {
|
|||
if (l["override"]["id"] !== undefined) {
|
||||
continue
|
||||
}
|
||||
context.enters("layers", i).err("A layer which changes the source-tags must also change the ID")
|
||||
context
|
||||
.enters("layers", i)
|
||||
.err("A layer which changes the source-tags must also change the ID")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,7 +400,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
|
|||
super(
|
||||
"Various consistency checks on the raw JSON",
|
||||
new MiscThemeChecks(),
|
||||
new OverrideShadowingCheck(),
|
||||
new OverrideShadowingCheck()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +410,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
|
|||
super(
|
||||
"The `if`-part in a mapping might set some keys. Those keys are not allowed to be set in the `addExtraTags`, as this might result in conflicting values",
|
||||
[],
|
||||
"DetectConflictingAddExtraTags",
|
||||
"DetectConflictingAddExtraTags"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -430,7 +437,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
|
|||
.enters("mappings", i)
|
||||
.err(
|
||||
"AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " +
|
||||
duplicateKeys.join(", "),
|
||||
duplicateKeys.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -448,13 +455,13 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
|
|||
super(
|
||||
"A tagRendering might set a freeform key (e.g. `name` and have an option that _should_ erase this name, e.g. `noname=yes`). Under normal circumstances, every mapping/freeform should affect all touched keys",
|
||||
[],
|
||||
"DetectNonErasedKeysInMappings",
|
||||
"DetectNonErasedKeysInMappings"
|
||||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: QuestionableTagRenderingConfigJson,
|
||||
context: ConversionContext,
|
||||
context: ConversionContext
|
||||
): QuestionableTagRenderingConfigJson {
|
||||
if (json.multiAnswer) {
|
||||
// No need to check this here, this has its own validation
|
||||
|
@ -508,8 +515,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
|
|||
.enters("freeform")
|
||||
.warn(
|
||||
"The freeform block does not modify the key `" +
|
||||
neededKey +
|
||||
"` which is set in a mapping. Use `addExtraTags` to overwrite it",
|
||||
neededKey +
|
||||
"` which is set in a mapping. Use `addExtraTags` to overwrite it"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -527,8 +534,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
|
|||
.enters("mappings", i)
|
||||
.warn(
|
||||
"This mapping does not modify the key `" +
|
||||
neededKey +
|
||||
"` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it",
|
||||
neededKey +
|
||||
"` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -552,7 +559,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
|
|||
* DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"]
|
||||
*/
|
||||
private static extractCalculatedTagNames(
|
||||
layerConfig?: LayerConfigJson | { calculatedTags: string[] },
|
||||
layerConfig?: LayerConfigJson | { calculatedTags: string[] }
|
||||
) {
|
||||
return (
|
||||
layerConfig?.calculatedTags?.map((ct) => {
|
||||
|
@ -638,16 +645,16 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
|
|||
json.mappings[i]["hideInAnswer"] !== true
|
||||
) {
|
||||
context.warn(
|
||||
`Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`,
|
||||
`Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`
|
||||
)
|
||||
} else if (doesMatch) {
|
||||
// The current mapping is shadowed!
|
||||
context.err(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
|
||||
The mapping ${parsedConditions[i].asHumanString(
|
||||
false,
|
||||
false,
|
||||
{},
|
||||
)} is fully matched by a previous mapping (namely ${j}), which matches:
|
||||
false,
|
||||
false,
|
||||
{}
|
||||
)} is fully matched by a previous mapping (namely ${j}), which matches:
|
||||
${parsedConditions[j].asHumanString(false, false, {})}.
|
||||
|
||||
To fix this problem, you can try to:
|
||||
|
@ -674,7 +681,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
|
|||
super(
|
||||
"Checks that 'then'clauses in mappings don't have images, but use 'icon' instead",
|
||||
[],
|
||||
"DetectMappingsWithImages",
|
||||
"DetectMappingsWithImages"
|
||||
)
|
||||
this._doesImageExist = doesImageExist
|
||||
}
|
||||
|
@ -714,14 +721,14 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
|
|||
if (!ignore) {
|
||||
ctx.err(
|
||||
`A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": <your-image>\` instead. The images found are ${images.join(
|
||||
", ",
|
||||
)}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`,
|
||||
", "
|
||||
)}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`
|
||||
)
|
||||
} else {
|
||||
ctx.info(
|
||||
`Ignored image ${images.join(
|
||||
", ",
|
||||
)} in 'then'-clause of a mapping as this check has been disabled`,
|
||||
", "
|
||||
)} in 'then'-clause of a mapping as this check has been disabled`
|
||||
)
|
||||
|
||||
for (const image of images) {
|
||||
|
@ -742,7 +749,7 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin
|
|||
super(
|
||||
"Given a possible set of translations, validates that <a href=... target='_blank'> does have `rel='noopener'` set",
|
||||
[],
|
||||
"ValidatePossibleLinks",
|
||||
"ValidatePossibleLinks"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -772,21 +779,21 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin
|
|||
|
||||
convert(
|
||||
json: string | Record<string, string>,
|
||||
context: ConversionContext,
|
||||
context: ConversionContext
|
||||
): string | Record<string, string> {
|
||||
if (typeof json === "string") {
|
||||
if (this.isTabnabbingProne(json)) {
|
||||
context.err(
|
||||
"The string " +
|
||||
json +
|
||||
" has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping",
|
||||
json +
|
||||
" has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
for (const k in json) {
|
||||
if (this.isTabnabbingProne(json[k])) {
|
||||
context.err(
|
||||
`The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`,
|
||||
`The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -804,7 +811,7 @@ class CheckTranslation extends DesugaringStep<Translatable> {
|
|||
super(
|
||||
"Checks that a translation is valid and internally consistent",
|
||||
["*"],
|
||||
"CheckTranslation",
|
||||
"CheckTranslation"
|
||||
)
|
||||
this._allowUndefined = allowUndefined
|
||||
}
|
||||
|
@ -850,17 +857,17 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
|
||||
convert(
|
||||
json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
|
||||
context: ConversionContext,
|
||||
context: ConversionContext
|
||||
): TagRenderingConfigJson {
|
||||
if (json["special"] !== undefined) {
|
||||
context.err(
|
||||
"Detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`",
|
||||
'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
|
||||
)
|
||||
}
|
||||
|
||||
if (Object.keys(json).length === 1 && typeof json["render"] === "string") {
|
||||
context.warn(
|
||||
`use the content directly instead of {render: ${JSON.stringify(json["render"])}}`,
|
||||
`use the content directly instead of {render: ${JSON.stringify(json["render"])}}`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -869,13 +876,32 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
CheckTranslation.allowUndefined.convert(json[key], context.enter(key))
|
||||
}
|
||||
for (let i = 0; i < json.mappings?.length ?? 0; i++) {
|
||||
const mapping = json.mappings[i]
|
||||
const mapping: MappingConfigJson = json.mappings[i]
|
||||
CheckTranslation.noUndefined.convert(
|
||||
mapping.then,
|
||||
context.enters("mappings", i, "then"),
|
||||
context.enters("mappings", i, "then")
|
||||
)
|
||||
if (!mapping.if) {
|
||||
context.enters("mappings", i).err("No `if` is defined")
|
||||
console.log(
|
||||
"Checking mappings",
|
||||
i,
|
||||
"if",
|
||||
mapping.if,
|
||||
context.path.join("."),
|
||||
mapping.then
|
||||
)
|
||||
context.enters("mappings", i, "if").err("No `if` is defined")
|
||||
}
|
||||
if (mapping.addExtraTags) {
|
||||
for (let j = 0; j < mapping.addExtraTags.length; j++) {
|
||||
if (!mapping.addExtraTags[j]) {
|
||||
context
|
||||
.enters("mappings", i, "addExtraTags", j)
|
||||
.err(
|
||||
"Detected a 'null' or 'undefined' value. Either specify a tag or delete this item"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
const en = mapping?.then?.["en"]
|
||||
if (en && this.detectYesOrNo(en)) {
|
||||
|
@ -883,18 +909,18 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
context
|
||||
.enters("mappings", i, "then")
|
||||
.warn(
|
||||
"A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' <i>without</i> the question, resulting in a weird phrasing in the information box",
|
||||
"A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' <i>without</i> the question, resulting in a weird phrasing in the information box"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json["group"]) {
|
||||
context.err("Groups are deprecated, use `\"label\": [\"" + json["group"] + "\"]` instead")
|
||||
context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead')
|
||||
}
|
||||
|
||||
if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) {
|
||||
context.err(
|
||||
"A question is defined, but no mappings nor freeform (key) are. Add at least one of them",
|
||||
"A question is defined, but no mappings nor freeform (key) are. Add at least one of them"
|
||||
)
|
||||
}
|
||||
if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) {
|
||||
|
@ -904,7 +930,15 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
context
|
||||
.enter("questionHint")
|
||||
.err(
|
||||
"A questionHint is defined, but no question is given. As such, the questionHint will never be shown",
|
||||
"A questionHint is defined, but no question is given. As such, the questionHint will never be shown"
|
||||
)
|
||||
}
|
||||
|
||||
if (json.icon?.["size"]) {
|
||||
context
|
||||
.enters("icon", "size")
|
||||
.err(
|
||||
"size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -914,10 +948,10 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
.enter("render")
|
||||
.err(
|
||||
"This tagRendering allows to set a value to key " +
|
||||
json.freeform.key +
|
||||
", but does not define a `render`. Please, add a value here which contains `{" +
|
||||
json.freeform.key +
|
||||
"}`",
|
||||
json.freeform.key +
|
||||
", but does not define a `render`. Please, add a value here which contains `{" +
|
||||
json.freeform.key +
|
||||
"}`"
|
||||
)
|
||||
} else {
|
||||
const render = new Translation(<any>json.render)
|
||||
|
@ -948,7 +982,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
const keyFirstArg = ["canonical", "fediverse_link", "translated"]
|
||||
if (
|
||||
keyFirstArg.some(
|
||||
(funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0,
|
||||
(funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0
|
||||
)
|
||||
) {
|
||||
continue
|
||||
|
@ -971,7 +1005,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
context
|
||||
.enter("render")
|
||||
.err(
|
||||
`The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!`,
|
||||
`The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -979,8 +1013,8 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
if (json.render && json["question"] && json.freeform === undefined) {
|
||||
context.err(
|
||||
`Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation(
|
||||
json["question"],
|
||||
).textFor("en")}`,
|
||||
json["question"]
|
||||
).textFor("en")}`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -991,13 +1025,16 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
.enters("freeform", "type")
|
||||
.err(
|
||||
"Unknown type: " +
|
||||
freeformType +
|
||||
"; try one of " +
|
||||
Validators.availableTypes.join(", "),
|
||||
freeformType +
|
||||
"; try one of " +
|
||||
Validators.availableTypes.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (context.hasErrors()) {
|
||||
return undefined
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
|
@ -1017,6 +1054,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
|
|||
constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) {
|
||||
super(
|
||||
"Various validation on tagRenderingConfigs",
|
||||
new MiscTagRenderingChecks(),
|
||||
new DetectShadowedMappings(layerConfig),
|
||||
new DetectConflictingAddExtraTags(),
|
||||
// TODO enable new DetectNonErasedKeysInMappings(),
|
||||
|
@ -1025,7 +1063,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
|
|||
new On("question", new ValidatePossibleLinks()),
|
||||
new On("questionHint", new ValidatePossibleLinks()),
|
||||
new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))),
|
||||
new MiscTagRenderingChecks(),
|
||||
new MiscTagRenderingChecks()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1040,7 +1078,12 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
private readonly _studioValidations: boolean
|
||||
private readonly _validatePointRendering = new ValidatePointRendering()
|
||||
|
||||
constructor(path: string, isBuiltin, doesImageExist, studioValidations) {
|
||||
constructor(
|
||||
path: string,
|
||||
isBuiltin: boolean,
|
||||
doesImageExist: DoesImageExist,
|
||||
studioValidations: boolean
|
||||
) {
|
||||
super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer")
|
||||
this._path = path
|
||||
this._isBuiltin = isBuiltin
|
||||
|
@ -1065,7 +1108,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
context
|
||||
.enter("source")
|
||||
.err(
|
||||
"No source section is defined; please define one as data is not loaded otherwise",
|
||||
"No source section is defined; please define one as data is not loaded otherwise"
|
||||
)
|
||||
} else {
|
||||
if (json.source === "special" || json.source === "special:library") {
|
||||
|
@ -1073,7 +1116,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
context
|
||||
.enters("source", "osmTags")
|
||||
.err(
|
||||
"No osmTags defined in the source section - these should always be present, even for geojson layer",
|
||||
"No osmTags defined in the source section - these should always be present, even for geojson layer"
|
||||
)
|
||||
} else {
|
||||
const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags")
|
||||
|
@ -1082,7 +1125,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
.enters("source", "osmTags")
|
||||
.err(
|
||||
"The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" +
|
||||
osmTags.asHumanString(false, false, {}),
|
||||
osmTags.asHumanString(false, false, {})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1108,10 +1151,10 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
.enter("syncSelection")
|
||||
.err(
|
||||
"Invalid sync-selection: must be one of " +
|
||||
LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") +
|
||||
" but got '" +
|
||||
json.syncSelection +
|
||||
"'",
|
||||
LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") +
|
||||
" but got '" +
|
||||
json.syncSelection +
|
||||
"'"
|
||||
)
|
||||
}
|
||||
if (json["pointRenderings"]?.length > 0) {
|
||||
|
@ -1129,7 +1172,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
context.enter("pointRendering").err("There are no pointRenderings at all...")
|
||||
}
|
||||
|
||||
json.pointRendering?.forEach((pr, i) => this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)))
|
||||
json.pointRendering?.forEach((pr, i) =>
|
||||
this._validatePointRendering.convert(pr, context.enters("pointeRendering", i))
|
||||
)
|
||||
|
||||
if (json["mapRendering"]) {
|
||||
context.enter("mapRendering").err("This layer has a legacy 'mapRendering'")
|
||||
|
@ -1145,8 +1190,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
if (!Constants.priviliged_layers.find((x) => x == json.id)) {
|
||||
context.err(
|
||||
"Layer " +
|
||||
json.id +
|
||||
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer",
|
||||
json.id +
|
||||
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1156,24 +1201,24 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
|
||||
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
|
||||
new On("tagRendering", new Each(new ValidateTagRenderings(json)))
|
||||
new On("tagRenderings", new Each(new ValidateTagRenderings(json)))
|
||||
if (json.title === undefined && json.source !== "special:library") {
|
||||
context
|
||||
.enter("title")
|
||||
.err(
|
||||
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error.",
|
||||
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
|
||||
)
|
||||
}
|
||||
if (json.title === null) {
|
||||
context.info(
|
||||
"Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set.",
|
||||
"Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
// Check for multiple, identical builtin questions - usability for studio users
|
||||
const duplicates = Utils.Duplicates(
|
||||
<string[]>json.tagRenderings.filter((tr) => typeof tr === "string"),
|
||||
<string[]>json.tagRenderings.filter((tr) => typeof tr === "string")
|
||||
)
|
||||
for (let i = 0; i < json.tagRenderings.length; i++) {
|
||||
const tagRendering = json.tagRenderings[i]
|
||||
|
@ -1203,7 +1248,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
{
|
||||
// duplicate ids in tagrenderings check
|
||||
const duplicates = Utils.NoNull(
|
||||
Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))),
|
||||
Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"])))
|
||||
)
|
||||
if (duplicates.length > 0) {
|
||||
// It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
|
||||
|
@ -1241,8 +1286,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
if (json["overpassTags"] !== undefined) {
|
||||
context.err(
|
||||
"Layer " +
|
||||
json.id +
|
||||
"still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)",
|
||||
json.id +
|
||||
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)'
|
||||
)
|
||||
}
|
||||
const forbiddenTopLevel = [
|
||||
|
@ -1262,7 +1307,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
|
||||
context.err(
|
||||
"Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'",
|
||||
"Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1279,9 +1324,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
if (this._path != undefined && this._path.indexOf(expected) < 0) {
|
||||
context.err(
|
||||
"Layer is in an incorrect place. The path is " +
|
||||
this._path +
|
||||
", but expected " +
|
||||
expected,
|
||||
this._path +
|
||||
", but expected " +
|
||||
expected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1299,13 +1344,13 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
.enter(["tagRenderings", ...emptyIndexes])
|
||||
.err(
|
||||
`Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join(
|
||||
",",
|
||||
)}])`,
|
||||
","
|
||||
)}])`
|
||||
)
|
||||
}
|
||||
|
||||
const duplicateIds = Utils.Duplicates(
|
||||
(json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions"),
|
||||
(json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions")
|
||||
)
|
||||
if (duplicateIds.length > 0 && !Utils.runningFromConsole) {
|
||||
context
|
||||
|
@ -1329,7 +1374,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
if (json.tagRenderings !== undefined) {
|
||||
new On(
|
||||
"tagRenderings",
|
||||
new Each(new ValidateTagRenderings(json, this._doesImageExist)),
|
||||
new Each(new ValidateTagRenderings(json, this._doesImageExist))
|
||||
).convert(json, context)
|
||||
}
|
||||
|
||||
|
@ -1356,7 +1401,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
context
|
||||
.enters("pointRendering", i, "marker", indexM, "icon", "condition")
|
||||
.err(
|
||||
"Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead.",
|
||||
"Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1394,9 +1439,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
.enters("presets", i, "tags")
|
||||
.err(
|
||||
"This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
|
||||
tags.asHumanString(false, false, {}) +
|
||||
"\n The required tags are: " +
|
||||
baseTags.asHumanString(false, false, {}),
|
||||
tags.asHumanString(false, false, {}) +
|
||||
"\n The required tags are: " +
|
||||
baseTags.asHumanString(false, false, {})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1413,7 +1458,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
|
|||
isBuiltin: boolean,
|
||||
doesImageExist: DoesImageExist,
|
||||
studioValidations: boolean = false,
|
||||
skipDefaultLayers: boolean = false,
|
||||
skipDefaultLayers: boolean = false
|
||||
) {
|
||||
super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig")
|
||||
this.validator = new ValidateLayer(
|
||||
|
@ -1421,7 +1466,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
|
|||
isBuiltin,
|
||||
doesImageExist,
|
||||
studioValidations,
|
||||
skipDefaultLayers,
|
||||
skipDefaultLayers
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1446,21 +1491,23 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> {
|
|||
}
|
||||
|
||||
if (json["markers"]) {
|
||||
context.enter("markers").err(`Detected a field 'markerS' in pointRendering. It is written as a singular case`)
|
||||
context
|
||||
.enter("markers")
|
||||
.err(
|
||||
`Detected a field 'markerS' in pointRendering. It is written as a singular case`
|
||||
)
|
||||
}
|
||||
if (json.marker && !Array.isArray(json.marker)) {
|
||||
context.enter("marker").err(
|
||||
"The marker in a pointRendering should be an array",
|
||||
)
|
||||
context.enter("marker").err("The marker in a pointRendering should be an array")
|
||||
}
|
||||
if (json.location.length == 0) {
|
||||
context.enter("location").err(
|
||||
"A pointRendering should have at least one 'location' to defined where it should be rendered. ",
|
||||
)
|
||||
context
|
||||
.enter("location")
|
||||
.err(
|
||||
"A pointRendering should have at least one 'location' to defined where it should be rendered. "
|
||||
)
|
||||
}
|
||||
return json
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1476,31 +1523,30 @@ export class ValidateLayer extends Conversion<
|
|||
isBuiltin: boolean,
|
||||
doesImageExist: DoesImageExist,
|
||||
studioValidations: boolean = false,
|
||||
skipDefaultLayers: boolean = false,
|
||||
skipDefaultLayers: boolean = false
|
||||
) {
|
||||
super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer")
|
||||
this._prevalidation = new PrevalidateLayer(
|
||||
path,
|
||||
isBuiltin,
|
||||
doesImageExist,
|
||||
studioValidations,
|
||||
studioValidations
|
||||
)
|
||||
this._skipDefaultLayers = skipDefaultLayers
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: ConversionContext,
|
||||
context: ConversionContext
|
||||
): { parsed: LayerConfig; raw: LayerConfigJson } {
|
||||
context = context.inOperation(this.name)
|
||||
if (typeof json === "string") {
|
||||
context.err(
|
||||
`Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`,
|
||||
`Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
if (this._skipDefaultLayers && Constants.added_by_default.indexOf(<any>json.id) >= 0) {
|
||||
return { parsed: undefined, raw: json }
|
||||
}
|
||||
|
@ -1527,7 +1573,7 @@ export class ValidateLayer extends Conversion<
|
|||
context
|
||||
.enters("calculatedTags", i)
|
||||
.err(
|
||||
`Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}`,
|
||||
`Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1578,8 +1624,8 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
|
|||
.enters("fields", i)
|
||||
.err(
|
||||
`Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from(
|
||||
Validators.availableTypes,
|
||||
).join(",")}`,
|
||||
Validators.availableTypes
|
||||
).join(",")}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1596,13 +1642,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{
|
|||
super(
|
||||
"Tries to detect layers where a shared filter can be used (or where similar filters occur)",
|
||||
[],
|
||||
"DetectDuplicateFilters",
|
||||
"DetectDuplicateFilters"
|
||||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] },
|
||||
context: ConversionContext,
|
||||
context: ConversionContext
|
||||
): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } {
|
||||
const { layers, themes } = json
|
||||
const perOsmTag = new Map<
|
||||
|
@ -1666,7 +1712,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
|
|||
filter: FilterConfigJson
|
||||
}[]
|
||||
>,
|
||||
layout?: LayoutConfigJson | undefined,
|
||||
layout?: LayoutConfigJson | undefined
|
||||
): void {
|
||||
if (layer.filter === undefined || layer.filter === null) {
|
||||
return
|
||||
|
@ -1706,7 +1752,7 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
super(
|
||||
"Detects mappings which have identical (english) names or identical mappings.",
|
||||
["presets"],
|
||||
"DetectDuplicatePresets",
|
||||
"DetectDuplicatePresets"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1717,13 +1763,13 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
if (new Set(enNames).size != enNames.length) {
|
||||
const dups = Utils.Duplicates(enNames)
|
||||
const layersWithDup = json.layers.filter((l) =>
|
||||
l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0),
|
||||
l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0)
|
||||
)
|
||||
const layerIds = layersWithDup.map((l) => l.id)
|
||||
context.err(
|
||||
`This themes has multiple presets which are named:${dups}, namely layers ${layerIds.join(
|
||||
", ",
|
||||
)} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`,
|
||||
", "
|
||||
)} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1738,17 +1784,17 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
Utils.SameObject(presetATags, presetBTags) &&
|
||||
Utils.sameList(
|
||||
presetA.preciseInput.snapToLayers,
|
||||
presetB.preciseInput.snapToLayers,
|
||||
presetB.preciseInput.snapToLayers
|
||||
)
|
||||
) {
|
||||
context.err(
|
||||
`This themes has multiple presets with the same tags: ${presetATags.asHumanString(
|
||||
false,
|
||||
false,
|
||||
{},
|
||||
{}
|
||||
)}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[
|
||||
j
|
||||
].title.textFor("en")}'`,
|
||||
].title.textFor("en")}'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1758,21 +1804,35 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
export class ValidateThemeEnsemble extends Conversion<LayoutConfig[], Map<string, {
|
||||
tags: TagsFilter,
|
||||
foundInTheme: string[]
|
||||
}>> {
|
||||
export class ValidateThemeEnsemble extends Conversion<
|
||||
LayoutConfig[],
|
||||
Map<
|
||||
string,
|
||||
{
|
||||
tags: TagsFilter
|
||||
foundInTheme: string[]
|
||||
}
|
||||
>
|
||||
> {
|
||||
constructor() {
|
||||
super("Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", [], "ValidateThemeEnsemble")
|
||||
super(
|
||||
"Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes",
|
||||
[],
|
||||
"ValidateThemeEnsemble"
|
||||
)
|
||||
}
|
||||
|
||||
convert(json: LayoutConfig[], context: ConversionContext): Map<string, {
|
||||
tags: TagsFilter,
|
||||
foundInTheme: string[]
|
||||
}> {
|
||||
|
||||
|
||||
const idToSource = new Map<string, { tags: TagsFilter, foundInTheme: string[] }>()
|
||||
convert(
|
||||
json: LayoutConfig[],
|
||||
context: ConversionContext
|
||||
): Map<
|
||||
string,
|
||||
{
|
||||
tags: TagsFilter
|
||||
foundInTheme: string[]
|
||||
}
|
||||
> {
|
||||
const idToSource = new Map<string, { tags: TagsFilter; foundInTheme: string[] }>()
|
||||
|
||||
for (const theme of json) {
|
||||
for (const layer of theme.layers) {
|
||||
|
@ -1804,16 +1864,18 @@ export class ValidateThemeEnsemble extends Conversion<LayoutConfig[], Map<string
|
|||
oldTheme.push(theme.id)
|
||||
continue
|
||||
}
|
||||
context.err(["The layer with id '" + id + "' is found in multiple themes with different tag definitions:",
|
||||
"\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}),
|
||||
"\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}),
|
||||
|
||||
|
||||
].join("\n"))
|
||||
context.err(
|
||||
[
|
||||
"The layer with id '" +
|
||||
id +
|
||||
"' is found in multiple themes with different tag definitions:",
|
||||
"\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}),
|
||||
"\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}),
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return idToSource
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { Utils } from "../../../Utils"
|
|||
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
|
||||
import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import { render } from "sass"
|
||||
|
||||
export default class ValidationUtils {
|
||||
public static getAllSpecialVisualisations(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue