diff --git a/.github/workflows/show_specification_annotations.yml b/.github/workflows/show_specification_annotations.yml new file mode 100644 index 00000000..824dd63e --- /dev/null +++ b/.github/workflows/show_specification_annotations.yml @@ -0,0 +1,23 @@ +name: Show Specification Annotations + +on: + pull_request: + paths: + - 'tests/**' + +jobs: + annotate: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Generate Annotations + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Run Python script + run: | + pip install uritemplate + python bin/annotation_workflow.py diff --git a/bin/annotation_workflow.py b/bin/annotation_workflow.py new file mode 100644 index 00000000..df789d73 --- /dev/null +++ b/bin/annotation_workflow.py @@ -0,0 +1,75 @@ +import json, re +from pathlib import Path +import uritemplate + +def github_action_notice(path, url, line): + """ + Return a GitHub action notice with file path, URL, and line number. + + Parameters: + path (str): File path. + url (str): URL. + line (int): Line number. + """ + return f"::warning file={path},line={line}::Annotation: {url}" + +def find_test_line_number(test_content, test_name): + """ + Find the line number of a test in the JSON content. + + Parameters: + test_content (str): JSON content. + test_name (str): Test name. + + Returns: + int: Line number of the test. + """ + lines = test_content.split("\n") + for i, line in enumerate(lines, start=1): + if test_name in line: + return i + return 1 + +def clear_previous_annotations(): + """ + Clear previous GitHub action annotations. + """ + print("::remove-matcher owner=me::") + +json_file_path = Path("bin/specification_urls.json") + +BIN_DIR = Path(__file__).parent +urls = json.loads(BIN_DIR.joinpath("specification_urls.json").read_text()) + +clear_previous_annotations() + +for file_path in Path("tests").rglob("*.json"): + + with file_path.open("r", encoding="utf-8") as f: + changed_file_content = f.read() + + try: + json_content = json.loads(changed_file_content) + except json.JSONDecodeError: + print(f"::error file={file_path}::Failed to parse JSON content") + + for test in json_content: + if "specification" in test: + line_number = find_test_line_number(changed_file_content, test.get("description") ) + + for specification_object in test["specification"]: + for spec, section in specification_object.items(): + draft = file_path.parent.name + if spec in ["quote"]: + continue + elif spec in ["core", "validation", "hyper-schema"]: + template = uritemplate.URITemplate(urls[draft][spec]) + elif re.match("^rfc\\d+$", spec): + template = uritemplate.URITemplate(urls["rfc"]) + elif re.match("^iso\\d+$", spec): + template = uritemplate.URITemplate(urls["iso"]) + else: + template = uritemplate.URITemplate(urls[spec]) + url = template.expand(spec=spec, section=section) + + print(github_action_notice(file_path, url, line_number)) diff --git a/bin/specification_urls.json b/bin/specification_urls.json new file mode 100644 index 00000000..faa60ea9 --- /dev/null +++ b/bin/specification_urls.json @@ -0,0 +1,34 @@ +{ + "draft3": { + "core": "https://json-schema.org/draft-03/draft-zyp-json-schema-03.pdf" + }, + "draft4": { + "core": "https://json-schema.org/draft-04/draft-zyp-json-schema-04#rfc.section.{section}", + "validation": "https://json-schema.org/draft-04/draft-fge-json-schema-validation-00#rfc.section.{section}", + "hyper-schema": "https://json-schema.org/draft-04/draft-luff-json-hyper-schema-00#rfc.section.{section}" + }, + "draft6": { + "core": "https://json-schema.org/draft-06/draft-wright-json-schema-01#rfc.section.{section}", + "validation": "https://json-schema.org/draft-06/draft-wright-json-schema-validation-01#rfc.section.{section}", + "hyper-schema": "https://json-schema.org/draft-06/draft-wright-json-schema-hyperschema-01#rfc.section.{section}" + }, + "draft7": { + "core": "https://json-schema.org/draft-07/draft-handrews-json-schema-01#rfc.section.{section}", + "validation": "https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01#rfc.section.{section}", + "hyper-schema": "https://json-schema.org/draft-07/draft-handrews-json-schema-hyperschema-01#rfc.section.{section}" + }, + "draft2019-09": { + "core": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02#rfc.section.{section}", + "validation": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-validation-02#rfc.section.{section}", + "hyper-schema": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-hyperschema-02#rfc.section.{section}" + }, + "draft2020-12": { + "core": "https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#section-{section}", + "validation": "https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01#section-{section}", + "hyper-schema": "https://json-schema.org/draft/2019-09/draft-handrews-json-schema-hyperschema-02#rfc.section.{section}" + }, + "ecma262": "https://262.ecma-international.org/{section}", + "perl5": "https://perldoc.perl.org/perlre#{section}", + "rfc": "https://www.rfc-editor.org/rfc/{spec}.txt#{section}", + "iso": "https://www.iso.org/obp/ui" +} diff --git a/tests/draft2020-12/additionalProperties.json b/tests/draft2020-12/additionalProperties.json index 9618575e..dadea948 100644 --- a/tests/draft2020-12/additionalProperties.json +++ b/tests/draft2020-12/additionalProperties.json @@ -1,7 +1,6 @@ [ { - "description": - "additionalProperties being false does not allow other properties", + "description": "additionalProperties being false does not allow other properties", "specification": [ { "core":"10.3.2.3", "quote": "The value of \"additionalProperties\" MUST be a valid JSON Schema. Boolean \"false\" forbids everything." } ], "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema",