chromium/third_party/blink/renderer/bindings/scripts/validate_web_idl.py

# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Runs the validator of Web IDL files using web_idl.Database.
"""

import optparse
import sys

import validator
import validator.rules
import web_idl


def parse_options():
    parser = optparse.OptionParser()
    parser.add_option("--web_idl_database",
                      type="string",
                      help="filepath of the input database")
    parser.add_option("--idl_syntax_known_issues",
                      type="string",
                      help="filepath of the idl errors already known")
    parser.add_option("--output",
                      type="string",
                      help="filepath of the file for the purpose of timestamp")
    options, args = parser.parse_args()

    required_option_names = [
        "web_idl_database",
        "idl_syntax_known_issues",
    ]
    for required_option_name in required_option_names:
        if getattr(options, required_option_name) is None:
            parser.error(
                "--{} is a required option.".format(required_option_name))

    return options, args


def create_known_issues(filepath):
    known_issues = {}
    with open(filepath) as file_obj:
        for line_number, line in enumerate(file_obj):
            comment_start = line.find("#")
            if comment_start != -1:
                line = line[:comment_start]

            blocks = line.split()
            if not blocks:
                continue
            assert len(blocks) == 2, (
                "{}: {}\n"
                "A line should have exactly 2 items, "
                "RULE_NAME and DOTTED_IDL_NAME (except for a comment with '#')"
                .format(filepath, line_number + 1))
            rule_name = blocks[0]
            target_path = blocks[1]
            known_issues.setdefault(target_path, []).append(rule_name)
    return known_issues


def should_suppress_error(known_issues, rule, target_path):
    return rule.__class__.__name__ in known_issues.get(target_path, [])


def main():
    options, args = parse_options()

    known_issues = create_known_issues(options.idl_syntax_known_issues)
    error_counts = [0]
    skipped_error_counts = [0]

    def report_error(rule, target, target_type, error_message):
        error_counts[0] += 1
        target_path = target_type.get_target_path(target)
        if should_suppress_error(known_issues, rule, target_path):
            skipped_error_counts[0] += 1
        else:
            debug_infos = target_type.get_debug_info_list(target)
            sys.stderr.write("violated rule: {}\n".format(
                rule.__class__.__name__))
            sys.stderr.write("target path  : {}\n".format(target_path))
            for i, debug_info in enumerate(debug_infos):
                if i == 0:
                    sys.stderr.write("related files: {}\n".format(
                        str(debug_info.location)))
                else:
                    sys.stderr.write("               {}\n".format(
                        str(debug_info.location)))
            sys.stderr.write("error message: {}\n\n".format(error_message))

    # Register rules
    rule_store = validator.RuleStore()
    validator.rules.register_all_rules(rule_store)

    # Validate
    database = web_idl.file_io.read_pickle_file(options.web_idl_database)
    validator_instance = validator.Validator(database)
    validator_instance.execute(rule_store, report_error)

    # Report errors
    if error_counts[0] - skipped_error_counts[0] > 0:
        sys.exit("Error: Some IDL files violate the Web IDL rules")

    # Create a file for the purpose of timestamp of a successful completion.
    if options.output:
        with open(options.output, mode="w") as file_obj:
            file_obj.write("""\
# This file was created just for the purpose of timestamp of a successful
# completion, mainly in order to corporate with a build system.


""")
            file_obj.write("Command line arguments:\n")
            file_obj.write("  --web_idl_database = {}\n".format(
                options.web_idl_database))
            file_obj.write("  --idl_syntax_known_issues = {}\n".format(
                options.idl_syntax_known_issues))
            file_obj.write("Results:\n  No new error\n")


if __name__ == '__main__':
    main()