From 5b78d2b840c8d731eb10131c941cbed4b58596b9 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Mon, 14 Sep 2026 14:38:40 -0700 Subject: [PATCH 01/17] Allow a / or . separated path, like find() allows. --- widlparser/parser.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/widlparser/parser.py b/widlparser/parser.py index 9eb2a1d..0b1db44 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -282,6 +282,12 @@ def normalized_method_name(self, method_text: str, interface_name: (str | None) name = method_text argument_names = None + if (not interface_name): + if "/" in name: + interface_name, name = name.split("/", 1) + elif "." in name: + interface_name, name = name.split(".", 1) + if (interface_name): interface = self.find(interface_name) if (interface): @@ -316,6 +322,12 @@ def normalized_method_names(self, method_text: str, interface_name: (str | None) name = method_text argument_names = None + if (not interface_name): + if "/" in name: + interface_name, name = name.split("/", 1) + elif "." in name: + interface_name, name = name.split(".", 1) + if (interface_name): interface = self.find(interface_name) if (interface): From fb5125bfc6f0293f6764216bcde7e7c2a1f56f1c Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Mon, 14 Sep 2026 14:41:51 -0700 Subject: [PATCH 02/17] Make find() and find_all() use a consistent result ordering, so find() == find_all()[0] --- widlparser/parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 0b1db44..f53fd38 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -214,7 +214,7 @@ def find_all(self, name: str) -> list[Construct]: """ Find all constructs with a given name. - Searches entire tree. + Searches entire tree in reverse order. """ match = re.match(r'(.*)\(.*\)(.*)', name) # strip ()'s while (match): @@ -251,6 +251,7 @@ def find_all(self, name: str) -> list[Construct]: argument = construct.find_argument(argument_name, False) if (argument): result.append(argument) + result.reverse() return result for construct in self.constructs: @@ -265,6 +266,7 @@ def find_all(self, name: str) -> list[Construct]: for construct in self.constructs: result += construct.find_arguments(name) + result.reverse() return result def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str: From d505132991b9065a2f915360cf3d011a8c2e3ffe Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Mon, 14 Sep 2026 15:54:11 -0700 Subject: [PATCH 03/17] Add find_method() and find_methods() to Parser, with the path/arg handling of normalized_method_name() --- widlparser/parser.py | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/widlparser/parser.py b/widlparser/parser.py index f53fd38..2033b10 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -269,6 +269,82 @@ def find_all(self, name: str) -> list[Construct]: result.reverse() return result + def find_method(self, method_text: str) -> Construct | None: + """ + Find all methods with a given name, and matching args if passed. + """ + + match = re.match(r"(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?", method_text) + if (not match): + return [] + interface_name, name, arg_text = match.groups() + + if (arg_text): + tokens = Tokenizer(arg_text) + if (productions.ArgumentList.peek(tokens)): + arguments = productions.ArgumentList(tokens, None) + arg_text = arguments.argument_names[0] + argument_names = [argument.strip() for argument in arg_text.split(',')] + else: + argument_names = None + + if (interface_name): + interface = self.find(interface_name) + if (interface): + return interface.find_method(name, argument_names) + return None + + construct: (Construct | None) + for construct in self.constructs: + method = construct.find_method(name, argument_names) + if (method): + return method + + construct = self.find(name) + if (construct and ('method' == construct.idl_type)): + return construct + + return None + + def find_methods(self, method_text: str) -> list[Construct]: + """ + Find all methods with a given name, and matching args if passed. + """ + + match = re.match(r"(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?", method_text) + if (not match): + return [] + interface_name, name, arg_text = match.groups() + + if (arg_text): + tokens = Tokenizer(arg_text) + if (productions.ArgumentList.peek(tokens)): + arguments = productions.ArgumentList(tokens, None) + arg_text = arguments.argument_names[0] + argument_names = [argument.strip() for argument in arg_text.split(',')] + else: + argument_names = None + + if (interface_name): + interface = self.find(interface_name) + if (interface): + return interface.find_methods(name, argument_names) + return [] + + construct: (Construct | None) + for construct in self.constructs: + methods = construct.find_methods(name, argument_names) + if (methods): + return methods + + construct = self.find(name) + if (construct and ('method' == construct.idl_type)): + return [construct] + + return [] + + + def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str: """Return normalized name for a method description.""" argument_names: (list[str] | None) From 0474a76e60ee22a1b7bc7c275cfae246847a8028 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 14:03:44 -0700 Subject: [PATCH 04/17] Be consistent with find/find_all and reverse results here too. --- widlparser/parser.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 2033b10..f6435c5 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -295,7 +295,7 @@ def find_method(self, method_text: str) -> Construct | None: return None construct: (Construct | None) - for construct in self.constructs: + for construct in reversed(self.constructs): method = construct.find_method(name, argument_names) if (method): return method @@ -328,13 +328,14 @@ def find_methods(self, method_text: str) -> list[Construct]: if (interface_name): interface = self.find(interface_name) if (interface): - return interface.find_methods(name, argument_names) + return list(reversed(interface.find_methods(name, argument_names))) return [] construct: (Construct | None) for construct in self.constructs: methods = construct.find_methods(name, argument_names) if (methods): + methods.reverse() return methods construct = self.find(name) From da74af351cbaa81969d6522f8187160b497fdff4 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 14:04:32 -0700 Subject: [PATCH 05/17] If there's a ( we're *definitely* looking for a method, don't fall down at all. --- widlparser/parser.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index f6435c5..9caca23 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -156,10 +156,10 @@ def find(self, name: str) -> (Construct | None): Searches entire tree in reverse order. """ - match = re.match(r'(.*)\(.*\)(.*)', name) # strip ()'s - while (match): - name = match.group(1) + match.group(2) - match = re.match(r'(.*)\(.*\)(.*)', name) + + # Specialize on methods + if "(" in name: + return self.find_method(name) path = None if ('/' in name): @@ -216,10 +216,10 @@ def find_all(self, name: str) -> list[Construct]: Searches entire tree in reverse order. """ - match = re.match(r'(.*)\(.*\)(.*)', name) # strip ()'s - while (match): - name = match.group(1) + match.group(2) - match = re.match(r'(.*)\(.*\)(.*)', name) + + # Specialize on methods + if "(" in name: + return self.find_methods(name) path = None if ('/' in name): From 2c26980ebb8c3ebe194509a7f92f45855407df1b Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 14:05:51 -0700 Subject: [PATCH 06/17] Normalize whitespace --- widlparser/parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 9caca23..78c7587 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -344,8 +344,6 @@ def find_methods(self, method_text: str) -> list[Construct]: return [] - - def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str: """Return normalized name for a method description.""" argument_names: (list[str] | None) From c1fd9894655381dc4a1888fbf892c7d18bdffc9f Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 14:12:34 -0700 Subject: [PATCH 07/17] Document some non-obvious details of normalized_method_names --- widlparser/parser.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 78c7587..0aebd53 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -345,7 +345,15 @@ def find_methods(self, method_text: str) -> list[Construct]: return [] def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str: - """Return normalized name for a method description.""" + """ + Return normalized name for a method description. + If passed a full WebIDL signature, normalize purely based on the parsed text. + Otherwise, try to find the construct in the parser and use that to normalize; + if that fails, then do a naive normalization based on the text. + The interface for a method is optional, + can be specified in the method_text as a path (like `find()`) + or as a separate argument. + """ argument_names: (list[str] | None) match = re.match(r'(.*)\((.*)\)(.*)', method_text) if (match): From 193f63035dab0ff7cb6892c2a5a0a990071ec6d6 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 14:30:53 -0700 Subject: [PATCH 08/17] Document find_method/s() --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 6f25d54..ca91a55 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,14 @@ Return a named construct. If a single name is provided, a breadth-first search t Return a list of named constructs. Accepts the same search paths as Parser.find(name). +**Parser.find_method(name)** + +Returns a specific method. Uses the path syntax of `Parser.find()`, and also takes arguments into account if possible (normalizing as per `Parser.normalized_method_name()`), to distinguish between overloads. If multiple methods match the passed string, returns the last such one. + +**Parser.find_methods(name)** + +Returns all matching methods. + **Parser.normalized_method_name(name [, interface_name=None])** Provide a normalized version of a method name, including the names of all arguments, e.g. "drawCircle(long x, long y, long radius)" becomes: "drawCircle(x, y, radius)". If a valid set of arguments is passed, the passed argument names will be returned in the normalized form. Otherwise, a search is performed for a matching previously parsed method name. The search may be narrowed to a particular interface by passing the name fo the interface or callbak in interface_name. From 96d56d3f7ffb75445d20c3916e3fd371f9fda171 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 14:31:02 -0700 Subject: [PATCH 09/17] fix lint errors --- widlparser/parser.py | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 0aebd53..013749c 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -156,9 +156,8 @@ def find(self, name: str) -> (Construct | None): Searches entire tree in reverse order. """ - # Specialize on methods - if "(" in name: + if '(' in name: return self.find_method(name) path = None @@ -216,9 +215,8 @@ def find_all(self, name: str) -> list[Construct]: Searches entire tree in reverse order. """ - # Specialize on methods - if "(" in name: + if '(' in name: return self.find_methods(name) path = None @@ -270,13 +268,10 @@ def find_all(self, name: str) -> list[Construct]: return result def find_method(self, method_text: str) -> Construct | None: - """ - Find all methods with a given name, and matching args if passed. - """ - - match = re.match(r"(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?", method_text) + """Find a method with a given name, and matching args if passed.""" + match = re.match(r'(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?', method_text) if (not match): - return [] + return None interface_name, name, arg_text = match.groups() if (arg_text): @@ -307,11 +302,8 @@ def find_method(self, method_text: str) -> Construct | None: return None def find_methods(self, method_text: str) -> list[Construct]: - """ - Find all methods with a given name, and matching args if passed. - """ - - match = re.match(r"(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?", method_text) + """Find all methods with a given name, and matching args if passed.""" + match = re.match(r'(?:([^./(]+)[./])?([^./(]+)(?:\((.*)\))?', method_text) if (not match): return [] interface_name, name, arg_text = match.groups() @@ -347,6 +339,7 @@ def find_methods(self, method_text: str) -> list[Construct]: def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str: """ Return normalized name for a method description. + If passed a full WebIDL signature, normalize purely based on the parsed text. Otherwise, try to find the construct in the parser and use that to normalize; if that fails, then do a naive normalization based on the text. @@ -368,10 +361,10 @@ def normalized_method_name(self, method_text: str, interface_name: (str | None) argument_names = None if (not interface_name): - if "/" in name: - interface_name, name = name.split("/", 1) - elif "." in name: - interface_name, name = name.split(".", 1) + if '/' in name: + interface_name, name = name.split('/', 1) + elif '.' in name: + interface_name, name = name.split('.', 1) if (interface_name): interface = self.find(interface_name) @@ -408,10 +401,10 @@ def normalized_method_names(self, method_text: str, interface_name: (str | None) argument_names = None if (not interface_name): - if "/" in name: - interface_name, name = name.split("/", 1) - elif "." in name: - interface_name, name = name.split(".", 1) + if '/' in name: + interface_name, name = name.split('/', 1) + elif '.' in name: + interface_name, name = name.split('.', 1) if (interface_name): interface = self.find(interface_name) From 6d1dffe25ca1ed9bc4e5631507ac82ddc9df6c3e Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Thu, 17 Sep 2026 15:02:32 -0700 Subject: [PATCH 10/17] Allow an empty arglist to *first* look for explicitly empty args, then just not care about args. --- widlparser/parser.py | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 013749c..3592352 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -274,24 +274,32 @@ def find_method(self, method_text: str) -> Construct | None: return None interface_name, name, arg_text = match.groups() - if (arg_text): + empty_args = False # Empty args might indicate explicitly zero arguments, or just args not passed + if (arg_text is not None): tokens = Tokenizer(arg_text) if (productions.ArgumentList.peek(tokens)): arguments = productions.ArgumentList(tokens, None) arg_text = arguments.argument_names[0] - argument_names = [argument.strip() for argument in arg_text.split(',')] + argument_names = [argument.strip() for argument in arg_text.split(',') if argument.strip() != ''] + if len(argument_names) == 0: + empty_args = True else: argument_names = None if (interface_name): interface = self.find(interface_name) if (interface): - return interface.find_method(name, argument_names) + method = interface.find_method(name, argument_names) + if (not method and empty_args): + method = interface.find_method(name) + return method return None construct: (Construct | None) for construct in reversed(self.constructs): method = construct.find_method(name, argument_names) + if (not method and empty_args): + method = construct.find_method(name) if (method): return method @@ -308,27 +316,39 @@ def find_methods(self, method_text: str) -> list[Construct]: return [] interface_name, name, arg_text = match.groups() - if (arg_text): + empty_args = False # Empty args might indicate explicitly zero arguments, or just args not passed + if (arg_text is not None): tokens = Tokenizer(arg_text) if (productions.ArgumentList.peek(tokens)): arguments = productions.ArgumentList(tokens, None) arg_text = arguments.argument_names[0] - argument_names = [argument.strip() for argument in arg_text.split(',')] + argument_names = [argument.strip() for argument in arg_text.split(',') if argument.strip() != ''] + if len(argument_names) == 0: + empty_args = True else: argument_names = None if (interface_name): interface = self.find(interface_name) if (interface): - return list(reversed(interface.find_methods(name, argument_names))) + methods = interface.find_methods(name, argument_names) + if (not methods and empty_args): + methods = interface.find_methods(name) + methods.reverse() + return methods return [] + result = [] construct: (Construct | None) for construct in self.constructs: methods = construct.find_methods(name, argument_names) + if (not methods and empty_args): + methods = construct.find_methods(name) if (methods): - methods.reverse() - return methods + result.extend(methods) + if (result): + result.reverse() + return result construct = self.find(name) if (construct and ('method' == construct.idl_type)): From ee1d8aff6139e9268e686840b17027f16d9a6054 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 20:30:03 +0000 Subject: [PATCH 11/17] Whoops, match paren style. --- widlparser/parser.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 3592352..730bcb6 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -281,7 +281,7 @@ def find_method(self, method_text: str) -> Construct | None: arguments = productions.ArgumentList(tokens, None) arg_text = arguments.argument_names[0] argument_names = [argument.strip() for argument in arg_text.split(',') if argument.strip() != ''] - if len(argument_names) == 0: + if (len(argument_names) == 0): empty_args = True else: argument_names = None @@ -323,7 +323,7 @@ def find_methods(self, method_text: str) -> list[Construct]: arguments = productions.ArgumentList(tokens, None) arg_text = arguments.argument_names[0] argument_names = [argument.strip() for argument in arg_text.split(',') if argument.strip() != ''] - if len(argument_names) == 0: + if (len(argument_names) == 0): empty_args = True else: argument_names = None @@ -381,9 +381,9 @@ def normalized_method_name(self, method_text: str, interface_name: (str | None) argument_names = None if (not interface_name): - if '/' in name: + if ('/' in name): interface_name, name = name.split('/', 1) - elif '.' in name: + elif ('.' in name): interface_name, name = name.split('.', 1) if (interface_name): @@ -421,9 +421,9 @@ def normalized_method_names(self, method_text: str, interface_name: (str | None) argument_names = None if (not interface_name): - if '/' in name: + if ('/' in name): interface_name, name = name.split('/', 1) - elif '.' in name: + elif ('.' in name): interface_name, name = name.split('.', 1) if (interface_name): From 8ecc582049a0c5d276c276f336b490134d503704 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 20:38:45 +0000 Subject: [PATCH 12/17] Remove the 'just by name' final search; if I've hit this point I already know the method isn't around. --- widlparser/parser.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 730bcb6..8197b0d 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -295,7 +295,6 @@ def find_method(self, method_text: str) -> Construct | None: return method return None - construct: (Construct | None) for construct in reversed(self.constructs): method = construct.find_method(name, argument_names) if (not method and empty_args): @@ -303,10 +302,6 @@ def find_method(self, method_text: str) -> Construct | None: if (method): return method - construct = self.find(name) - if (construct and ('method' == construct.idl_type)): - return construct - return None def find_methods(self, method_text: str) -> list[Construct]: @@ -339,7 +334,6 @@ def find_methods(self, method_text: str) -> list[Construct]: return [] result = [] - construct: (Construct | None) for construct in self.constructs: methods = construct.find_methods(name, argument_names) if (not methods and empty_args): @@ -350,10 +344,6 @@ def find_methods(self, method_text: str) -> list[Construct]: result.reverse() return result - construct = self.find(name) - if (construct and ('method' == construct.idl_type)): - return [construct] - return [] def normalized_method_name(self, method_text: str, interface_name: (str | None) = None) -> str: From 134d68bf2acea36537d659bedad2dde13d2a5310 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 20:49:21 +0000 Subject: [PATCH 13/17] Remove remaining superfluous type sigs --- widlparser/parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/widlparser/parser.py b/widlparser/parser.py index 8197b0d..600f0f6 100644 --- a/widlparser/parser.py +++ b/widlparser/parser.py @@ -384,7 +384,6 @@ def normalized_method_name(self, method_text: str, interface_name: (str | None) return cast(str, method.method_name) return name + '(' + ', '.join(argument_names or []) + ')' - construct: (Construct | None) for construct in self.constructs: method = construct.find_method(name, argument_names) if (method): @@ -424,7 +423,6 @@ def normalized_method_names(self, method_text: str, interface_name: (str | None) return list(itertools.chain(*[method.method_names for method in methods])) return [name + '(' + ', '.join(argument_names or []) + ')'] - construct: (Construct | None) for construct in self.constructs: methods = construct.find_methods(name, argument_names) if (methods): From f2ea5056fcf91977541b7707141b37cf25cc1557 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 22:00:44 +0000 Subject: [PATCH 14/17] Treat constructors and methods equally in find_method/find_methods --- widlparser/constructs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/widlparser/constructs.py b/widlparser/constructs.py index 487e420..42f9e67 100644 --- a/widlparser/constructs.py +++ b/widlparser/constructs.py @@ -823,13 +823,13 @@ def find_members(self, name: str) -> list[Construct]: def find_method(self, name: str, argument_names: (Sequence[str] | None) = None) -> (Construct | None): for member in reversed(self.members): - if (('method' == member.idl_type) and (name == member.name) + if ((member.idl_type in ('method', 'constructor')) and (name == member.name) and ((argument_names is None) or member.matches_argument_names(argument_names))): return member return None def find_methods(self, name: str, argument_names: (Sequence[str] | None) = None) -> list[Construct]: - return [member for member in self.members if (('method' == member.idl_type) and (name == member.name) + return [member for member in self.members if ((member.idl_type in ('method', 'constructor')) and (name == member.name) and ((argument_names is None) or member.matches_argument_names(argument_names)))] def find_argument(self, name: str, search_members: bool = True) -> (Construct | None): @@ -1003,13 +1003,13 @@ def find_members(self, name: str) -> list[Construct]: def find_method(self, name: str, argument_names: (Sequence[str] | None) = None) -> (Construct | None): for member in reversed(self.members): - if (('method' == member.idl_type) and (name == member.name) + if ((member.idl_type in ('method', 'constructor')) and (name == member.name) and ((argument_names is None) or member.matches_argument_names(argument_names))): return member return None def find_methods(self, name: str, argument_names: (Sequence[str] | None) = None) -> list[Construct]: - return [member for member in self.members if (('method' == member.idl_type) and (name == member.name) + return [member for member in self.members if ((member.idl_type in ('method', 'constructor')) and (name == member.name) and ((argument_names is None) or member.matches_argument_names(argument_names)))] def find_argument(self, name: str, search_members: bool = True) -> (Construct | None): @@ -1255,13 +1255,13 @@ def find_members(self, name: str) -> list[Construct]: def find_method(self, name: str, argument_names: (Sequence[str] | None) = None) -> (Construct | None): for member in reversed(self.members): - if (('method' == member.idl_type) and (name == member.name) + if ((member.idl_type in ('method', 'constructor')) and (name == member.name) and ((argument_names is None) or member.matches_argument_names(argument_names))): return member return None def find_methods(self, name: str, argument_names: (Sequence[str] | None) = None) -> list[Construct]: - return [member for member in self.members if (('method' == member.idl_type) and (name == member.name) + return [member for member in self.members if ((member.idl_type in ('method', 'constructor')) and (name == member.name) and ((argument_names is None) or member.matches_argument_names(argument_names)))] def find_argument(self, name: str, search_members: bool = True) -> (Construct | None): From ce7e7adde6536dc3958e5a54a7310f4d9cdfe7dd Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 22:01:17 +0000 Subject: [PATCH 15/17] Expose the arguments in ExtendedAttributeArgList --- widlparser/constructs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/widlparser/constructs.py b/widlparser/constructs.py index 42f9e67..4534ea1 100644 --- a/widlparser/constructs.py +++ b/widlparser/constructs.py @@ -1965,6 +1965,10 @@ def normal_name(self) -> (str | None): + (', '.join(argument.name for argument in self._arguments if (argument.name)) if (self._arguments) else '') + ')') return self.attribute + @property + def arguments(self) -> ArgumentList: + return self._arguments + def _str(self) -> str: return str(self._attribute) + str(self._open_paren) + (str(self._arguments) if (self._arguments) else '') + str(self._close_paren) From cb5337ac05235a2652b67ab2e68c18a915b8636b Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 22:10:32 +0000 Subject: [PATCH 16/17] Update tests --- test-expected.txt | 8 ++++++-- test.py | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/test-expected.txt b/test-expected.txt index 3c2ec09..55e53a7 100644 --- a/test-expected.txt +++ b/test-expected.txt @@ -829,14 +829,18 @@ FIND: callMe/round Foo/method(x, y, inf, ...fooArg)/y Foo/method(string, foo) -Foo +foo +Foo/Foo() +Foo/Foo() +Foo/Foo(one) +Foo/MyConstructor() Foo/MyOtherConstructor(two, longest)/longest Foo/method(x, y, inf, ...fooArg)/fooArg Window LinkStyle/mediaText

unsigned long long

method(DOMString string, optional Foo foo = {});
-Foo/method(x, y, inf, ...fooArg) Foo/method(string, foo) +Foo/method(x, y, inf, ...fooArg) NORMALIZE: foo() unknown() diff --git a/test.py b/test.py index de75fde..020cf96 100755 --- a/test.py +++ b/test.py @@ -378,7 +378,11 @@ def test_difference(input, output): print(cast(Construct, parser.find('round')).full_name) print(cast(Construct, parser.find('Foo/method/y')).full_name) print(cast(Construct, parser.find('Foo.method')).full_name) - print(cast(Construct, parser.find('Foo(constructor)')).full_name) + print(cast(Construct, parser.find('foo')).full_name) + print(cast(Construct, parser.find('Foo()')).full_name) + print(cast(Construct, parser.find('Foo/Foo()')).full_name) + print(cast(Construct, parser.find('Foo(one)')).full_name) + print(cast(Construct, parser.find('MyConstructor()')).full_name) print(cast(Construct, parser.find('longest')).full_name) print(cast(Construct, parser.find('fooArg')).full_name) print(cast(Construct, parser.find('Window')).full_name) From c2fc2507bdabc8dd02d573f0d1688075d902efc0 Mon Sep 17 00:00:00 2001 From: Tab Atkins-Bittner Date: Tue, 22 Sep 2026 22:19:22 +0000 Subject: [PATCH 17/17] Add some find_method and find_methods tests --- test-expected.txt | 15 +++++++++++++++ test.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/test-expected.txt b/test-expected.txt index 55e53a7..1af6ad7 100644 --- a/test-expected.txt +++ b/test-expected.txt @@ -839,6 +839,21 @@ Foo/method(x, y, inf, ...fooArg)/fooArg Window LinkStyle/mediaText

unsigned long long

method(DOMString string, optional Foo foo = {});
+Foo/Foo() +Foo/Foo(one) +Foo/MyConstructor() +Namespace2/method(x, y) +Foo/method(string, foo) +Foo/method(string, foo) +Foo/method(string, foo) +Foo/method(string, foo) +Foo/method(x, y, inf, ...fooArg) +Foo/method(string, foo) +Foo/method(x, y, inf, ...fooArg) +Namespace2/method(x, y) +Namespace2/method(x) +Namespace1/method(x, y) +Namespace1/method(x) Foo/method(string, foo) Foo/method(x, y, inf, ...fooArg) NORMALIZE: diff --git a/test.py b/test.py index 020cf96..9c878f6 100755 --- a/test.py +++ b/test.py @@ -388,8 +388,23 @@ def test_difference(input, output): print(cast(Construct, parser.find('Window')).full_name) print(cast(Construct, parser.find('mediaText')).full_name) print(cast(Construct, parser.find('Foo.method')).markup(Marker())) + + print(cast(Construct, parser.find_method('Foo/Foo()')).full_name) + print(cast(Construct, parser.find_method('Foo(one)')).full_name) + print(cast(Construct, parser.find_method('MyConstructor()')).full_name) + print(cast(Construct, parser.find_method('method()')).full_name) + print(cast(Construct, parser.find_method('Foo/method()')).full_name) + print(cast(Construct, parser.find_method('method(string)')).full_name) + print(cast(Construct, parser.find_method('method(string, foo)')).full_name) + for method in parser.find_all('Foo.method'): print(method.full_name) + for method in parser.find_methods('Foo.method'): + print(method.full_name) + for method in parser.find_methods('method()'): + print(method.full_name) + + print("NORMALIZE:") print(parser.normalized_method_name('foo'))