diff --git a/lib/productions/interface.js b/lib/productions/interface.js index 202bf947..2abf7212 100644 --- a/lib/productions/interface.js +++ b/lib/productions/interface.js @@ -5,6 +5,7 @@ import { Constant } from "./constant.js"; import { IterableLike } from "./iterable.js"; import { stringifier } from "./helpers.js"; import { validationError } from "../error.js"; +import { checkInterfaceMemberDuplication } from "../validators/interface.js"; /** * @param {import("../tokeniser").Tokeniser} tokeniser @@ -52,5 +53,8 @@ for more information.`; yield validationError(this.source, this.tokens.name, this, message); } yield* super.validate(defs); + if (!this.partial) { + yield* checkInterfaceMemberDuplication(defs, this); + } } } diff --git a/lib/validator.js b/lib/validator.js index e247ae45..93baeb12 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -2,6 +2,24 @@ 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; +} + function groupDefinitions(all) { const unique = new Map(); const duplicates = new Set(); @@ -25,7 +43,13 @@ function groupDefinitions(all) { duplicates.add(def); } } - return { all, unique, partials, duplicates }; + return { + all, + unique, + partials, + duplicates, + mixinMap: getMixinMap(all, unique) + }; } function* checkDuplicatedNames({ unique, duplicates }) { @@ -36,61 +60,6 @@ function* checkDuplicatedNames({ unique, duplicates }) { } } -function* checkInterfaceMemberDuplication(defs) { - const interfaces = [...defs.unique.values()].filter(def => def.type === "interface"); - const includesMap = getIncludesMap(); - - for (const i of interfaces) { - yield* forEachInterface(i); - } - - function* forEachInterface(i) { - const opNames = new Set(getOperations(i).map(op => op.name)); - const partials = defs.partials.get(i.name) || []; - const mixins = includesMap.get(i.name) || []; - for (const ext of [...partials, ...mixins]) { - const additions = getOperations(ext); - yield* forEachExtension(additions, opNames, ext, i); - for (const addition of additions) { - opNames.add(addition.name); - } - } - } - - function* forEachExtension(additions, existings, ext, base) { - for (const addition of additions) { - const { name } = addition; - if (name && existings.has(name)) { - const message = `The operation "${name}" has already been defined for the base interface "${base.name}" either in itself or in a mixin`; - yield error(ext.source, addition.tokens.name, ext, message); - } - } - } - - function getOperations(i) { - return i.members - .filter(({type}) => type === "operation"); - } - - function getIncludesMap() { - const map = new Map(); - const includes = defs.all.filter(def => def.type === "includes"); - for (const include of includes) { - const array = map.get(include.target); - const mixin = defs.unique.get(include.includes); - if (!mixin) { - continue; - } - if (array) { - array.push(mixin); - } else { - map.set(include.target, [mixin]); - } - } - return map; - } -} - function* validateIterable(ast) { const defs = groupDefinitions(ast); for (const def of defs.all) { @@ -99,7 +68,6 @@ function* validateIterable(ast) { } } yield* checkDuplicatedNames(defs); - yield* checkInterfaceMemberDuplication(defs); } // Remove this once all of our support targets expose `.flat()` by default diff --git a/lib/validators/interface.js b/lib/validators/interface.js new file mode 100644 index 00000000..c71fe706 --- /dev/null +++ b/lib/validators/interface.js @@ -0,0 +1,29 @@ +import { validationError } from "../error.js"; + +export function* checkInterfaceMemberDuplication(defs, i) { + const opNames = new Set(getOperations(i).map(op => op.name)); + const partials = defs.partials.get(i.name) || []; + const mixins = defs.mixinMap.get(i.name) || []; + for (const ext of [...partials, ...mixins]) { + const additions = getOperations(ext); + yield* forEachExtension(additions, opNames, ext, i); + for (const addition of additions) { + opNames.add(addition.name); + } + } + + function* forEachExtension(additions, existings, ext, base) { + for (const addition of additions) { + const { name } = addition; + if (name && existings.has(name)) { + const message = `The operation "${name}" has already been defined for the base interface "${base.name}" either in itself or in a mixin`; + yield validationError(ext.source, addition.tokens.name, ext, message); + } + } + } + + function getOperations(i) { + return i.members + .filter(({type}) => type === "operation"); + } +}