Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/productions/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}
82 changes: 25 additions & 57 deletions lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 }) {
Expand All @@ -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) {
Expand 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
Expand Down
29 changes: 29 additions & 0 deletions lib/validators/interface.js
Original file line number Diff line number Diff line change
@@ -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");
}
}