blob: 0d99da8248b78d65f9ba92e6f928569d21d13431 [file] [log] [blame] [edit]
import { validationError as error } from "./error.js";
function getMixinMap(all, unique) {
const map = new Map();
const includes = all.filter((def) => def.type === "includes");
for (const include of includes) {
const mixin = unique.get(include.includes);
if (!mixin) {
continue;
}
const array = map.get(include.target);
if (array) {
array.push(mixin);
} else {
map.set(include.target, [mixin]);
}
}
return map;
}
/**
* @typedef {ReturnType<typeof groupDefinitions>} Definitions
*/
function groupDefinitions(all) {
const unique = new Map();
const duplicates = new Set();
const partials = new Map();
for (const def of all) {
if (def.partial) {
const array = partials.get(def.name);
if (array) {
array.push(def);
} else {
partials.set(def.name, [def]);
}
continue;
}
if (!def.name) {
continue;
}
if (!unique.has(def.name)) {
unique.set(def.name, def);
} else {
duplicates.add(def);
}
}
return {
all,
unique,
partials,
duplicates,
mixinMap: getMixinMap(all, unique),
cache: {
typedefIncludesDictionary: new WeakMap(),
dictionaryIncludesRequiredField: new WeakMap(),
},
};
}
function* checkDuplicatedNames({ unique, duplicates }) {
for (const dup of duplicates) {
const { name } = dup;
const message = `The name "${name}" of type "${
unique.get(name).type
}" was already seen`;
yield error(dup.tokens.name, dup, "no-duplicate", message);
}
}
function* validateIterable(ast) {
const defs = groupDefinitions(ast);
for (const def of defs.all) {
if (def.validate) {
yield* def.validate(defs);
}
}
yield* checkDuplicatedNames(defs);
}
// Remove this once all of our support targets expose `.flat()` by default
function flatten(array) {
if (array.flat) {
return array.flat();
}
return [].concat(...array);
}
/**
* @param {import("./productions/base.js").Base[]} ast
* @return {import("./error.js").WebIDLErrorData[]} validation errors
*/
export function validate(ast) {
return [...validateIterable(flatten(ast))];
}