| # Copyright 2023 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| load("./common.star", "compiled_tool_path", "get_fuchsia_dir", "os_exec") |
| |
| def _doc_checker(ctx): |
| """Runs the doc-checker tool.""" |
| if ctx.scm.root != get_fuchsia_dir(ctx): |
| # doc-checker is only relevant for fuchsia.git. |
| return |
| |
| affected_files = set(ctx.scm.affected_files()) |
| |
| # If a Markdown change is present (including a deletion of a markdown file), |
| # check the entire project. |
| if not any([f.endswith(".md") for f in affected_files]): |
| return |
| |
| exe = compiled_tool_path(ctx, "doc-checker") |
| res = os_exec(ctx, [exe, "--json", "--local-links-only", "--root", get_fuchsia_dir(ctx)], ok_retcodes = [0, 1]).wait() |
| findings = json.decode(res.stdout, default = None) |
| if not findings and res.retcode: |
| fail("doc-checker failed:\n%s" % res.stderr) |
| for finding in findings: |
| abspath = finding["doc_line"]["file_name"] |
| msg = finding["message"] |
| if finding["help_suggestion"]: |
| msg += "\n\n" + finding["help_suggestion"] |
| msg += "\n\nRun `fx doc-checker --local-links-only` to reproduce." |
| filepath = abspath[len(ctx.scm.root) + 1:] |
| level = { |
| "Info": "notice", |
| "Warning": "warning", |
| "Error": "error", |
| }[finding["level"]] |
| |
| # Normally we should only propagate findings if the referenced file is |
| # affected to avoid spamming users with non-blocking warnings about |
| # files they didn't touch. But errors may occur in unaffected files |
| # (e.g. deleting a file may make links in unaffected files invalid) so |
| # we should always propagate errors. |
| if filepath in affected_files or level == "error": |
| ctx.emit.finding( |
| filepath = filepath, |
| line = finding["doc_line"]["line_num"], |
| level = level, |
| message = msg, |
| ) |
| |
| def _mdlint(ctx): |
| """Runs mdlint.""" |
| rfc_dir = "docs/contribute/governance/rfcs/" |
| affected_files = set(ctx.scm.affected_files()) |
| if not any([f.startswith(rfc_dir) for f in affected_files]): |
| return |
| mdlint = compiled_tool_path(ctx, "mdlint") |
| res = os_exec( |
| ctx, |
| [ |
| mdlint, |
| "--json", |
| "--root-dir", |
| rfc_dir, |
| "--enable", |
| "all", |
| "--filter-filenames", |
| rfc_dir, |
| ], |
| ok_retcodes = [0, 1], |
| ).wait() |
| for finding in json.decode(res.stderr): |
| if finding["path"] not in ctx.scm.affected_files(): |
| continue |
| ctx.emit.finding( |
| level = "warning", |
| message = finding["message"], |
| filepath = finding["path"], |
| line = finding["start_line"], |
| end_line = finding["end_line"], |
| col = finding["start_char"] + 1, |
| end_col = finding["end_char"] + 1, |
| ) |
| |
| def _codelinks(ctx): |
| """Checks for certain malformatted links in source code.""" |
| for f, meta in ctx.scm.affected_files().items(): |
| # TODO(olivernewman): Files under //docs should generally reference |
| # other documentation files by path (e.g. "//docs/foo/bar.md") rather |
| # than URL, with some exceptions for reference docs. |
| if f.startswith("docs/"): |
| continue |
| for num, line in meta.new_lines(): |
| for match in ctx.re.allmatches( |
| r"(https?://)?fuchsia.googlesource.com/fuchsia/\+/(refs/heads/)?\w+/docs/(?P<path>\S+)\.md", |
| line, |
| ): |
| repl = "https://fuchsia.dev/fuchsia-src/" + match.groups[-1] |
| ctx.emit.finding( |
| level = "warning", |
| message = ( |
| "Documentation links should point to fuchsia.dev rather than " + |
| "fuchsia.googlesource.com. Consider changing this to %s." % repl |
| ), |
| filepath = f, |
| line = num, |
| col = match.offset + 1, |
| end_col = match.offset + 1 + len(match.groups[0]), |
| replacements = [repl], |
| ) |
| |
| def _rfcmeta(ctx): |
| """Validates RFC metadata.""" |
| files = [ |
| f |
| for f in ctx.scm.affected_files() |
| # Ignore files that aren't inside the RFC directory. |
| if f.startswith("docs/contribute/governance/rfcs/") |
| ] |
| if not files: |
| return |
| exe = compiled_tool_path(ctx, "rfcmeta") |
| res = os_exec(ctx, [ |
| exe, |
| "-checkout-dir", |
| get_fuchsia_dir(ctx), |
| ] + files).wait() |
| |
| for finding in json.decode(res.stdout): |
| ctx.emit.finding( |
| message = finding["message"], |
| level = "warning", |
| filepath = finding["path"], |
| line = finding["line"], |
| end_line = finding["end_line"], |
| col = finding["col"], |
| end_col = finding["end_col"], |
| replacements = finding["replacements"], |
| ) |
| |
| def register_doc_checks(): |
| shac.register_check(_codelinks) |
| shac.register_check(shac.check( |
| _doc_checker, |
| # TODO(olivernewman): doc-checker has historically been run from `fx |
| # format-code` even though it's not a formatter and doesn't write |
| # results back to disk. Determine whether anyone depends on doc-checker |
| # running with `fx format-code`, and unset `formatter = True`. |
| formatter = True, |
| )) |
| shac.register_check(_mdlint) |
| shac.register_check(_rfcmeta) |