forked from MapComplete/MapComplete
		
	Studio: improve error handling, fix renumbering
This commit is contained in:
		
							parent
							
								
									7afe58e6a5
								
							
						
					
					
						commit
						079a3f8694
					
				
					 10 changed files with 187 additions and 62 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(
 | 
			
		||||
| 
						 | 
				
			
			@ -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 ?? "") +
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
import { ConversionMessage, ConversionMsgLevel } from "./Conversion"
 | 
			
		||||
import { Context } from "maplibre-gl"
 | 
			
		||||
 | 
			
		||||
export class ConversionContext {
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +43,31 @@ export class ConversionContext {
 | 
			
		|||
        return new ConversionContext([], msg ? [msg] : [], ["test"])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static print(msg: ConversionMessage) {
 | 
			
		||||
        const noString = msg.context.path.filter(
 | 
			
		||||
            (p) => typeof p !== "string" && typeof p !== "number"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			@ -21,9 +24,7 @@ import PresetConfig from "../PresetConfig"
 | 
			
		|||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
 | 
			
		||||
import { Translatable } from "../Json/Translatable"
 | 
			
		||||
import { ConversionContext } from "./ConversionContext"
 | 
			
		||||
import * as eli from "../../../assets/editor-layer-index.json"
 | 
			
		||||
import { AvailableRasterLayers } from "../../RasterLayers"
 | 
			
		||||
import Back from "../../../assets/svg/Back.svelte"
 | 
			
		||||
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
 | 
			
		||||
 | 
			
		||||
class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +179,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
        if (!json.title) {
 | 
			
		||||
            context.enter("title").err(`The theme ${json.id} does not have a title defined.`)
 | 
			
		||||
        }
 | 
			
		||||
        if(!json.icon){
 | 
			
		||||
        if (!json.icon) {
 | 
			
		||||
            context.enter("icon").err("A theme should have an icon")
 | 
			
		||||
        }
 | 
			
		||||
        if (this._isBuiltin && this._extractImages !== undefined) {
 | 
			
		||||
| 
						 | 
				
			
			@ -831,6 +832,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
        json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
    ): TagRenderingConfigJson {
 | 
			
		||||
        console.log(">>> Validating TR", context.path.join("."), json)
 | 
			
		||||
        if (json["special"] !== undefined) {
 | 
			
		||||
            context.err(
 | 
			
		||||
                'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
 | 
			
		||||
| 
						 | 
				
			
			@ -848,13 +850,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")
 | 
			
		||||
                )
 | 
			
		||||
                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)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -977,6 +998,9 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (context.hasErrors()) {
 | 
			
		||||
            return undefined
 | 
			
		||||
        }
 | 
			
		||||
        return json
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -996,6 +1020,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(),
 | 
			
		||||
| 
						 | 
				
			
			@ -1003,8 +1028,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
 | 
			
		|||
            new On("render", new ValidatePossibleLinks()),
 | 
			
		||||
            new On("question", new ValidatePossibleLinks()),
 | 
			
		||||
            new On("questionHint", new ValidatePossibleLinks()),
 | 
			
		||||
            new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))),
 | 
			
		||||
            new MiscTagRenderingChecks()
 | 
			
		||||
            new On("mappings", new Each(new On("then", new ValidatePossibleLinks())))
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1107,7 +1131,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'")
 | 
			
		||||
| 
						 | 
				
			
			@ -1134,7 +1160,7 @@ 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")
 | 
			
		||||
| 
						 | 
				
			
			@ -1424,29 +1450,33 @@ 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ValidateLayer extends Conversion<
 | 
			
		||||
    LayerConfigJson,
 | 
			
		||||
    { parsed: LayerConfig; raw: LayerConfigJson }
 | 
			
		||||
> {
 | 
			
		||||
    private readonly _skipDefaultLayers: boolean
 | 
			
		||||
    private readonly _prevalidation: PrevalidateLayer
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        path: string,
 | 
			
		||||
        isBuiltin: boolean,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue