import WebIDL
def WebIDLTest(parser, harness):
def shouldPass(prefix, iface, expectedMembers, numProductions=1):
p = parser.reset()
p.parse(iface)
results = p.finish()
harness.check(
len(results),
numProductions,
f"{prefix} - Should have production count {numProductions}",
)
harness.ok(
isinstance(results[0], WebIDL.IDLInterface),
f"{prefix} - Should be an IDLInterface",
)
expectedMembers = list(expectedMembers)
for m in results[0].members:
name = m.identifier.name
member_type = type(m)
if m.isMethod() and m.isStatic():
harness.ok(True, f"{prefix} - {name} - Should be a {member_type}")
elif (name, member_type) in expectedMembers:
harness.ok(True, f"{prefix} - {name} - Should be a {member_type}")
expectedMembers.remove((name, member_type))
else:
harness.ok(
False,
f"{prefix} - {name} - Unknown symbol of type {member_type}",
)
if len(expectedMembers) == 0:
harness.ok(True, "Found all the members")
else:
harness.ok(
False,
f"Expected member not found: {expectedMembers[0][0]} of type {expectedMembers[0][1]}",
)
return results
def shouldFail(prefix, iface):
try:
p = parser.reset()
p.parse(iface)
p.finish()
harness.ok(False, prefix + " - Interface passed when should've failed")
except WebIDL.WebIDLError:
harness.ok(True, prefix + " - Interface failed as expected")
except Exception as e:
harness.ok(
False,
prefix + f" - Interface failed but not as a WebIDLError exception: {e}",
)
iterableMembers = [
(x, WebIDL.IDLMethod) for x in ["entries", "keys", "values", "forEach"]
]
setROMembers = (
[(x, WebIDL.IDLMethod) for x in ["has"]]
+ [("__setlike", WebIDL.IDLMaplikeOrSetlike)]
+ iterableMembers
)
setROMembers.extend([("size", WebIDL.IDLAttribute)])
setRWMembers = [
(x, WebIDL.IDLMethod) for x in ["add", "clear", "delete"]
] + setROMembers
mapROMembers = (
[(x, WebIDL.IDLMethod) for x in ["get", "has"]]
+ [("__maplike", WebIDL.IDLMaplikeOrSetlike)]
+ iterableMembers
)
mapROMembers.extend([("size", WebIDL.IDLAttribute)])
mapRWMembers = [
(x, WebIDL.IDLMethod) for x in ["set", "clear", "delete"]
] + mapROMembers
iterableMembers.append(("__iterable", WebIDL.IDLIterable))
asyncIterableMembers = [
(x, WebIDL.IDLMethod) for x in ["entries", "keys", "values"]
]
asyncIterableMembers.append(("__async_iterable", WebIDL.IDLAsyncIterable))
valueIterableMembers = [("__iterable", WebIDL.IDLIterable)]
valueIterableMembers.append(("__indexedgetter", WebIDL.IDLMethod))
valueIterableMembers.append(("length", WebIDL.IDLAttribute))
valueAsyncIterableMembers = [("__async_iterable", WebIDL.IDLAsyncIterable)]
valueAsyncIterableMembers.append(("values", WebIDL.IDLMethod))
disallowedIterableNames = [
("keys", WebIDL.IDLMethod),
("entries", WebIDL.IDLMethod),
("values", WebIDL.IDLMethod),
]
disallowedMemberNames = [
("forEach", WebIDL.IDLMethod),
("has", WebIDL.IDLMethod),
("size", WebIDL.IDLAttribute),
] + disallowedIterableNames
mapDisallowedMemberNames = [("get", WebIDL.IDLMethod)] + disallowedMemberNames
disallowedNonMethodNames = [
("clear", WebIDL.IDLMethod),
("delete", WebIDL.IDLMethod),
]
mapDisallowedNonMethodNames = [("set", WebIDL.IDLMethod)] + disallowedNonMethodNames
setDisallowedNonMethodNames = [("add", WebIDL.IDLMethod)] + disallowedNonMethodNames
unrelatedMembers = [
("unrelatedAttribute", WebIDL.IDLAttribute),
("unrelatedMethod", WebIDL.IDLMethod),
]
shouldPass(
"Iterable (key only)",
"""
interface Foo1 {
iterable<long>;
readonly attribute unsigned long length;
getter long(unsigned long index);
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueIterableMembers + unrelatedMembers,
)
shouldPass(
"Iterable (key only) inheriting from parent",
"""
interface Foo1 : Foo2 {
iterable<long>;
readonly attribute unsigned long length;
getter long(unsigned long index);
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueIterableMembers,
numProductions=2,
)
shouldPass(
"Iterable (key and value)",
"""
interface Foo1 {
iterable<long, long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
iterableMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Iterable (key and value) inheriting from parent",
"""
interface Foo1 : Foo2 {
iterable<long, long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
iterableMembers,
numProductions=3,
)
shouldPass(
"Async iterable (key only)",
"""
interface Foo1 {
async_iterable<long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueAsyncIterableMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Async iterable (key only) inheriting from parent",
"""
interface Foo1 : Foo2 {
async_iterable<long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueAsyncIterableMembers,
numProductions=3,
)
shouldPass(
"Async iterable with argument (key only)",
"""
interface Foo1 {
async_iterable<long>(optional long foo);
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
valueAsyncIterableMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Async iterable (key and value)",
"""
interface Foo1 {
async_iterable<long, long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
asyncIterableMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Async iterable (key and value) inheriting from parent",
"""
interface Foo1 : Foo2 {
async_iterable<long, long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
asyncIterableMembers,
numProductions=3,
)
shouldPass(
"Async iterable with argument (key and value)",
"""
interface Foo1 {
async_iterable<long, long>(optional long foo);
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
asyncIterableMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Maplike (readwrite)",
"""
interface Foo1 {
maplike<long, long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
mapRWMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Maplike (readwrite) inheriting from parent",
"""
interface Foo1 : Foo2 {
maplike<long, long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
mapRWMembers,
numProductions=3,
)
shouldPass(
"Maplike (readwrite)",
"""
interface Foo1 {
maplike<long, long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
mapRWMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Maplike (readwrite) inheriting from parent",
"""
interface Foo1 : Foo2 {
maplike<long, long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
mapRWMembers,
numProductions=3,
)
shouldPass(
"Maplike (readonly)",
"""
interface Foo1 {
readonly maplike<long, long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
mapROMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Maplike (readonly) inheriting from parent",
"""
interface Foo1 : Foo2 {
readonly maplike<long, long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
mapROMembers,
numProductions=3,
)
shouldPass(
"Setlike (readwrite)",
"""
interface Foo1 {
setlike<long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
setRWMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Setlike (readwrite) inheriting from parent",
"""
interface Foo1 : Foo2 {
setlike<long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
setRWMembers,
numProductions=3,
)
shouldPass(
"Setlike (readonly)",
"""
interface Foo1 {
readonly setlike<long>;
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
setROMembers + unrelatedMembers,
numProductions=2,
)
shouldPass(
"Setlike (readonly) inheriting from parent",
"""
interface Foo1 : Foo2 {
readonly setlike<long>;
};
interface Foo2 {
attribute long unrelatedAttribute;
long unrelatedMethod();
};
""",
setROMembers,
numProductions=3,
)
shouldPass(
"Inheritance of maplike/setlike",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
};
""",
mapRWMembers,
numProductions=3,
)
shouldFail(
"JS Implemented maplike interface",
"""
[JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"]
interface Foo1 {
constructor();
setlike<long>;
};
""",
)
shouldFail(
"JS Implemented maplike interface",
"""
[JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1"]
interface Foo1 {
constructor();
maplike<long, long>;
};
""",
)
shouldFail(
"Two maplike/setlikes on same interface",
"""
interface Foo1 {
setlike<long>;
maplike<long, long>;
};
""",
)
shouldFail(
"Two iterable/setlikes on same interface",
"""
interface Foo1 {
iterable<long>;
maplike<long, long>;
};
""",
)
shouldFail(
"Two iterables on same interface",
"""
interface Foo1 {
iterable<long>;
iterable<long, long>;
};
""",
)
shouldFail(
"Two iterables on same interface",
"""
interface Foo1 {
iterable<long>;
async_iterable<long>;
};
""",
)
shouldFail(
"Two iterables on same interface",
"""
interface Foo1 {
async_iterable<long>;
async_iterable<long, long>;
};
""",
)
shouldFail(
"Async iterable with non-optional arguments",
"""
interface Foo1 {
async_iterable<long>(long foo);
};
""",
)
shouldFail(
"Async iterable with non-optional arguments",
"""
interface Foo1 {
async_iterable<long>(optional long foo, long bar);
};
""",
)
shouldFail(
"Async iterable with non-optional arguments",
"""
interface Foo1 {
async_iterable<long, long>(long foo);
};
""",
)
shouldFail(
"Two maplike/setlikes in partials",
"""
interface Foo1 {
maplike<long, long>;
};
partial interface Foo1 {
setlike<long>;
};
""",
)
shouldFail(
"Conflicting maplike/setlikes across inheritance",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
setlike<long>;
};
""",
)
shouldFail(
"Conflicting maplike/iterable across inheritance",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
iterable<long>;
};
""",
)
shouldFail(
"Conflicting maplike/setlikes across multistep inheritance",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
};
interface Foo3 : Foo2 {
setlike<long>;
};
""",
)
def testConflictingMembers(
likeMember, conflict, expectedMembers, methodPasses, numProductions=1
):
(conflictName, conflictType) = conflict
if methodPasses:
shouldPass(
f"Conflicting method: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember};
[Throws]
undefined {conflictName}(long test1, double test2, double test3);
}};
""",
expectedMembers,
numProductions=numProductions,
)
else:
shouldFail(
f"Conflicting method: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember};
[Throws]
undefined {conflictName}(long test1, double test2, double test3);
}};
""",
)
shouldFail(
f"Conflicting inherited method: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
undefined {conflictName}(long test1, double test2, double test3);
}};
interface Foo2 : Foo1 {{
{likeMember};
}};
""",
)
if conflictType == WebIDL.IDLAttribute:
shouldFail(
f"Conflicting static method: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember};
static undefined {conflictName}(long test1, double test2, double test3);
}};
""",
)
else:
shouldPass(
f"Conflicting static method: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember};
static undefined {conflictName}(long test1, double test2, double test3);
}};
""",
expectedMembers,
numProductions=numProductions,
)
shouldFail(
f"Conflicting attribute: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember}
attribute double {conflictName};
}};
""",
)
shouldFail(
f"Conflicting const: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember};
const double {conflictName} = 0;
}};
""",
)
shouldFail(
f"Conflicting static attribute: {likeMember} and {conflictName}",
f"""
interface Foo1 {{
{likeMember};
static attribute long {conflictName};
}};
""",
)
for member in disallowedIterableNames:
testConflictingMembers(
"iterable<long, long>", member, iterableMembers, False, numProductions=2
)
for member in mapDisallowedMemberNames:
testConflictingMembers(
"maplike<long, long>", member, mapRWMembers, False, numProductions=2
)
for member in disallowedMemberNames:
testConflictingMembers(
"setlike<long>", member, setRWMembers, False, numProductions=2
)
for member in mapDisallowedNonMethodNames:
testConflictingMembers(
"maplike<long, long>", member, mapRWMembers, True, numProductions=2
)
for member in setDisallowedNonMethodNames:
testConflictingMembers(
"setlike<long>", member, setRWMembers, True, numProductions=2
)
shouldPass(
"Inheritance of maplike/setlike with child member collision",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
undefined entries();
};
""",
mapRWMembers,
numProductions=3,
)
shouldPass(
"Inheritance of multi-level maplike/setlike with child member collision",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
};
interface Foo3 : Foo2 {
undefined entries();
};
""",
mapRWMembers,
numProductions=4,
)
shouldFail(
"Maplike interface with mixin member collision",
"""
interface Foo1 {
maplike<long, long>;
};
interface mixin Foo2 {
undefined entries();
};
Foo1 includes Foo2;
""",
)
shouldPass(
"Inherited Maplike interface with consequential interface member collision",
"""
interface Foo1 {
maplike<long, long>;
};
interface mixin Foo2 {
undefined entries();
};
interface Foo3 : Foo1 {
};
Foo3 includes Foo2;
""",
mapRWMembers,
numProductions=5,
)
shouldFail(
"Inheritance of name collision with child maplike/setlike",
"""
interface Foo1 {
undefined entries();
};
interface Foo2 : Foo1 {
maplike<long, long>;
};
""",
)
shouldFail(
"Inheritance of multi-level name collision with child maplike/setlike",
"""
interface Foo1 {
undefined entries();
};
interface Foo2 : Foo1 {
};
interface Foo3 : Foo2 {
maplike<long, long>;
};
""",
)
shouldPass(
"Inheritance of attribute collision with parent maplike/setlike",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
attribute double size;
};
""",
mapRWMembers,
numProductions=3,
)
shouldPass(
"Inheritance of multi-level attribute collision with parent maplike/setlike",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
};
interface Foo3 : Foo2 {
attribute double size;
};
""",
mapRWMembers,
numProductions=4,
)
shouldFail(
"Inheritance of attribute collision with child maplike/setlike",
"""
interface Foo1 {
attribute double size;
};
interface Foo2 : Foo1 {
maplike<long, long>;
};
""",
)
shouldFail(
"Inheritance of multi-level attribute collision with child maplike/setlike",
"""
interface Foo1 {
attribute double size;
};
interface Foo2 : Foo1 {
};
interface Foo3 : Foo2 {
maplike<long, long>;
};
""",
)
shouldFail(
"Inheritance of attribute/rw function collision with child maplike/setlike",
"""
interface Foo1 {
attribute double set;
};
interface Foo2 : Foo1 {
maplike<long, long>;
};
""",
)
shouldFail(
"Inheritance of const/rw function collision with child maplike/setlike",
"""
interface Foo1 {
const double set = 0;
};
interface Foo2 : Foo1 {
maplike<long, long>;
};
""",
)
shouldPass(
"Inheritance of rw function with same name in child maplike/setlike",
"""
interface Foo1 {
maplike<long, long>;
};
interface Foo2 : Foo1 {
undefined clear();
};
""",
mapRWMembers,
numProductions=3,
)
shouldFail(
"Inheritance of unforgeable attribute collision with child maplike/setlike",
"""
interface Foo1 {
[LegacyUnforgeable]
attribute double size;
};
interface Foo2 : Foo1 {
maplike<long, long>;
};
""",
)
shouldFail(
"Inheritance of multi-level unforgeable attribute collision with child maplike/setlike",
"""
interface Foo1 {
[LegacyUnforgeable]
attribute double size;
};
interface Foo2 : Foo1 {
};
interface Foo3 : Foo2 {
maplike<long, long>;
};
""",
)
shouldPass(
"Interface with readonly allowable overrides",
"""
interface Foo1 {
readonly setlike<long>;
readonly attribute boolean clear;
};
""",
setROMembers + [("clear", WebIDL.IDLAttribute)],
numProductions=2,
)
r = shouldPass(
"Check proper override of clear/delete/set",
"""
interface Foo1 {
maplike<long, long>;
long clear(long a, long b, double c, double d);
long set(long a, long b, double c, double d);
long delete(long a, long b, double c, double d);
};
""",
mapRWMembers,
numProductions=2,
)
for m in r[0].members:
if m.identifier.name in ["clear", "set", "delete"]:
harness.ok(m.isMethod(), f"{m.identifier.name} should be a method")
harness.check(
m.maxArgCount, 4, f"{m.identifier.name} should have 4 arguments"
)
harness.ok(
not m.isMaplikeOrSetlikeOrIterableMethod(),
f"{m.identifier.name} should not be a maplike/setlike function",
)
tests = [
("maplike", " maplike<long, long>;"),
("setlike", " readonly setlike<long>;"),
("iterable", " iterable<long>;"),
("async_iterable", " async_iterable<long, long>;"),
]
for name, line in tests:
parser = parser.reset()
lines = [
"interface Foo {",
line,
" readonly attribute unsigned long length;",
" getter long(unsigned long index);",
"};",
]
parser.parse("\n".join(lines))
results = parser.finish()
p = results[0].members[0]
harness.check(p.identifier.name, "__" + name, "Correct name")
colno = line.find(name)
loc_lines = str(p.identifier.location).split("\n")
harness.check(loc_lines[1], line, "Second line shows the input")
harness.check(
loc_lines[2],
" " * colno + "^",
"Correct column pointer in location string",
)