function validateAPIJSON(api) {

	// create map of defined symbols (built-in types, dependency libraries, current library)
	const defined = Object.assign(Object.create(null), builtinTypes, externalSymbols);
	if ( api.symbols ) {
		api.symbols.forEach((symbol) => {
			defined[symbol.name] = symbol;
		});
	}

	const naming = Object.create(null);
	const missing = Object.create(null);

	const rValidNames = /^[$A-Z_a-z][$0-9A-Z_a-z]*$/i;
	const rValidModuleNames = /^[$A-Z_a-z][$\-\.0-9A-Z_a-z]*$/i;

	let currentTypeParameters = Object.create(null);

	function checkName(name, hint) {
		if ( !rValidNames.test(name) ) {
			naming[name] = naming[name] || [];
			naming[name].push(hint);
		}
	}

	/**
	 * Checks the name of a property in an object-like structure.
	 * As in JavaScript, the rules for such names are less strict than
	 * for classes, interfaces or function declarations.
	 */
	function checkPropertyName(name, hint) {
		const match = rStartsWithValidPropertyName.exec(name);
		if ( match == null || match[0].length !== name.length ) {
			naming[name] ??= [];
			naming[name].push(hint);
		}
	}

	function checkModuleName(name, hint) {
		if ( !rValidModuleNames.test(name) ) {
			naming[name] = naming[name] || [];
			naming[name].push(hint);
		}
	}

	function checkCompoundName(name, hint) {

		if ( name.startsWith("module:") ) {
			const segments = name.slice("module:".length).split("/");

			// split last segment into a module name part and a symbol name part
			const p = segments[segments.length - 1].search(/[.~#]/);
			if ( p >= 0 ) {
				name = segments[segments.length - 1].slice(p + 1);
				segments[segments.length - 1] = segments[segments.length - 1].slice(0, p);
			}

			// check all module name parts
			segments.forEach((segment) => checkModuleName(segment, `path segment of ${hint}`));

			if ( p < 0 ) {
				// module name only, no export name to check
				return;
			}
		}

		name.split(/[.~#]/).forEach((segment) => checkName(segment, `name segment of ${hint}`));
	}

	function reportError(type, usage) {
		missing[type] = missing[type] || [];
		missing[type].push(usage);
	}

	function checkSimpleType(typeName, hint) {
		if (currentTypeParameters[typeName]) {
			return;
		}
		if ( !defined[typeName] ) {
			reportError(typeName, hint);
		}
	}

	function checkType(type, hint) {

		function _check(type) {
			if ( type == null ) {
				return;
			}
			switch (type.type) {
			case 'simpleType':
				checkSimpleType(type.name, hint);
				break;
			case 'array':
				_check(type.component);
				break;
			case 'object':
				_check(type.key);
				_check(type.value);
				break;
			case 'set':
				_check(type.element);
				break;
			case 'promise':
				_check(type.fulfill);
				break;
			case 'function':
				type.params.forEach(_check);
				_check(type.return);
				_check(type.this);
				_check(type.constructor);
				break;
			case 'structure':
				Object.keys(type.fields).forEach((key) => _check(type.fields[key]));
				break;
			case 'union':
				type.types.forEach(_check);
				break;
			case 'typeApplication':
				_check(type.baseType);
				type.templateTypes.forEach(_check);
				// TODO check number of templateTypes against declaration of baseType
				// requires JSDoc support of @template tag, which is currently missing
				break;
			default:
				break;
			}
		}
		try {
			// debug("normalize", type);
			type.type = typeParser.parse(type.type, typeNormalizer).str;
			// debug("check", type);
			const ast = typeParser.parse(type.type);
			_check(ast);
		} catch (e) {
			future(e);
			reportError(type.type, `failed to parse type of ${hint}`);
		}
	}

	function checkTypeParameter(typeParam, hint) {
		checkType(typeParam, `type constraint of ${hint}`);
		if ( typeParam.default ) {
			checkType({type: typeParam.default}, `default of ${hint}`);
		}
	}

	function checkParam(param, prefix, hint) {
		if (prefix) {
			// nested parameter properties follow the naming conventions for object properties
			checkPropertyName(param.name, `name of param ${prefix}${param.name} of ${hint}`);
		} else {
			checkName(param.name, `name of param ${prefix}${param.name} of ${hint}`);
		}
		checkType(param, `param ${prefix}${param.name} of ${hint}`);
		if ( param.parameterProperties ) {
			Object.keys(param.parameterProperties).forEach((sub) =>
				checkParam(param.parameterProperties[sub], prefix + param.name + ".", hint));
		}
	}

	function checkMethodSignature(method, hint, isConstructor = false) {
		const prevTypeParams = currentTypeParameters;
		if ( !isConstructor && method.typeParameters ) {
			// Note: type parameters of the constructor are handled on class level
			currentTypeParameters = Object.create(currentTypeParameters);
			for (const typeParam of method.typeParameters) {
				currentTypeParameters[typeParam.name] = typeParam;
			}
			// check type parameters in a 2nd loop so that they can depend on each other
			for (const typeParam of method.typeParameters) {
				checkTypeParameter(typeParam, `type parameter ${typeParam.name} of ${hint}`);
			}
		}
		if ( method.returnValue ) {
			checkType(method.returnValue, `return value of ${hint}`);
		}
		if ( method.parameters ) {
			method.parameters.forEach((param) => checkParam(param, '', hint));
		}
		if ( method.throws ) {
			method.throws.forEach((ex) => checkType(ex, `exception of ${hint}`));
		}
		// restore type parameters of outer scope
		currentTypeParameters = prevTypeParams;
	}

	function checkClassAgainstInterface(symbol, oIntfAPI) {
		if ( oIntfAPI.methods ) {
			oIntfAPI.methods.forEach((intfMethod) => {
				// search for method implementation
				const implMethod = symbol.methods && symbol.methods.find(
					(candidateMethod) => candidateMethod.name === intfMethod.name && !candidateMethod.static);

				if ( !implMethod ) {
					if ( !intfMethod.optional ) {
						reportError(oIntfAPI.name, `implementation of ${intfMethod.name} missing in ${symbol.name}`);
					}
				} else {
					if ( intfMethod.parameters ) {
						intfMethod.parameters.forEach((intfParam, idx) => {
							const implParam = implMethod.parameters && implMethod.parameters[idx];
							if ( !implParam ) {
								if ( !intfParam.optional ) {
									reportError(oIntfAPI.name, `parameter ${intfParam.name} missing in implementation of ${symbol.name}#${intfMethod.name}`);
								}
							} else if ( implParam.type !== intfParam.type ) {
								reportError(oIntfAPI.name, `type of parameter ${intfParam.name} of interface method differs from type in implementation ${symbol.name}#${intfMethod.name}`);
							}
							// TODO check nested properties
						});
					}
					if ( intfMethod.returnValue != null && implMethod.returnValue == null ) {
						reportError(oIntfAPI.name, `return value of interface method missing in implementation ${symbol.name}#${intfMethod}`);
					} else if ( intfMethod.returnValue == null && implMethod.returnValue != null ) {
						reportError(oIntfAPI.name, `while interface method is void, implementation ${symbol.name}#${intfMethod.name} returns a value`);
					} else if ( intfMethod.returnValue != null && implMethod.returnValue != null ) {
						if ( intfMethod.returnValue.type !== implMethod.returnValue.type ) {
							reportError(oIntfAPI.name, `return type of interface method differs from return type of implementation ${symbol.name}#${intfMethod.name}`);
						}
					}
				}
			});
		}
	}

	function checkEnum(symbol) {
		if ( symbol["ui5-metamodel"]
			 && !(symbol["ui5-metadata"] && symbol["ui5-metadata"].stereotype === "enum")
			 && Array.isArray(symbol.properties) && symbol.properties.length > 0 ) {
			reportError(symbol.name, "enum is metamodel relevant but keys and values differ");
		}
		checkCompoundName(symbol.name, `name of ${symbol.name}`);
		if ( symbol.properties ) {
			symbol.properties.forEach((prop) => {
				checkName(prop.name, `name of ${symbol.name}.${prop.name}`);
				if ( prop.type ) {
					checkType(prop, `type of ${symbol.name}.${prop.name}`);
				}
			});
		}
	}

	function checkClass(symbol) {
		const prevTypeParams = currentTypeParameters;
		if ( Object.hasOwn(symbol, "constructor") && symbol.constructor.typeParameters ) {
			currentTypeParameters = Object.create(currentTypeParameters);
			for (const typeParam of symbol.constructor.typeParameters) {
				currentTypeParameters[typeParam.name] = typeParam;
			}
			// check type oarameters in a 2nd loop so that they can depend on each other
			for (const typeParam of symbol.constructor.typeParameters) {
				checkTypeParameter(typeParam, `type parameter ${typeParam.name} of ${symbol.name}`);
			}
		}

		checkCompoundName(symbol.name, `name of ${symbol.name}`);
		if ( symbol.extends ) {
			checkSimpleType(symbol.extends, `base class of ${symbol.name}`);
		}
		if ( symbol.implements ) {
			symbol.implements.forEach((intf) => {
				checkSimpleType(intf, `interface of ${symbol.name}`);
				const oIntfAPI = defined[intf];
				if ( oIntfAPI ) {
					checkClassAgainstInterface(symbol, oIntfAPI);
				}
			});
		}
		if ( Object.hasOwn(symbol, "constructor") ) {
			checkMethodSignature(symbol.constructor, symbol.name + ".constructor", /* isConstructor */ true);
		}
		if ( symbol.properties ) {
			symbol.properties.forEach((prop) => {
				const qualifiedName = `${symbol.name}.${prop.name}`;
				checkPropertyName(prop.name, `name of ${qualifiedName}`);
				if ( prop.type ) {
					checkType(prop, `type of ${qualifiedName}`);
				}
			});
		}
		if ( symbol.methods ) {
			symbol.methods.forEach((method) => {
				const qualifiedName = `${symbol.name}.${method.name}`;
				checkName(method.name, `name of ${qualifiedName}`);
				checkMethodSignature(method, qualifiedName);
			});
		}
		if ( symbol.events ) {
			symbol.events.forEach((event) => {
				const qualifiedName = `${symbol.name}.${event.name}`;
				checkName(event.name, `name of ${qualifiedName}`);
				if ( event.parameters ) {
					event.parameters.forEach((param) => {
						checkParam(param, '', qualifiedName);
					});
				}
			});
		}
		// restore type parameters of outer scope
		currentTypeParameters = prevTypeParams;
	}

	api.symbols.forEach((symbol) => {
		if ( symbol.kind === 'function' ) {
			checkCompoundName(symbol.name, `name of ${symbol.nam}`);
			checkMethodSignature(symbol, symbol.name);
		} else if ( symbol.kind === 'enum' ) {
			checkEnum(symbol);
		} else {
			checkClass(symbol);
		}
	});

	if ( Object.keys(missing).length > 0 || Object.keys(naming).length > 0 ) {
		future("API validation errors:"); // TODO decide on level

		Object.keys(missing).forEach((type) => {
			if ( Array.isArray(missing[type]) ) {
				if ( Object.hasOwn(erroneousTypes, type.toLowerCase()) ) {
					error(`type '${type}' (use '${erroneousTypes[type.toLowerCase()]}' instead)`);
					missing[type].forEach((usage) => error(`  ${usage}`));
				} else {
					future(`type '${type}'`);
					missing[type].forEach((usage) => future(`  ${usage}`));
				}
			}
		});
		Object.keys(naming).forEach((name) => {
			if ( Array.isArray(naming[name]) ) {
				future(`invalid name '${name}'`);
				naming[name].forEach((usage) => future(`  ${usage}`));
			}
		});
	} else {
		info("API validation succeeded.");
	}

}
Wrong, but commonly used type names and their correct replacement.