Skip to content

Commit

Permalink
Extend tools/cross/format.py to handle JS too
Browse files Browse the repository at this point in the history
  • Loading branch information
npaun committed Aug 20, 2024
1 parent 4bed3a6 commit ab15158
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 1 deletion.
34 changes: 34 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Lint

on:
pull_request:
push:
branches:
- main

jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
show-progress: false
- name: Setup Linux
run: |
export DEBIAN_FRONTEND=noninteractive
wget https://apt.llvm.org/llvm.sh
sed -i '/apt-get install/d' llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 18
sudo apt-get install -y --no-install-recommends clang-format-18
- name: Install pnpm
uses: pnpm/action-setup@v4
# The pnpm version will be determined by the `packageManager` field in `.npmrc`
- name: Install project deps with pnpm
run: |
pnpm i
- name: Lint
run: |
python3 ./tools/cross/format.py --check
env:
CLANG_FORMAT: clang-format-18
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
samples/nodejs-compat-streams-split2/split2.js
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"name": "@cloudflare/workerd-root",
"private": true,
"scripts": {
"lint": "eslint types/src"
"lint": "eslint types/src",
"format": "prettier"
},
"dependencies": {
"capnp-ts": "^0.7.0",
Expand Down
184 changes: 184 additions & 0 deletions tools/cross/format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env python3

import logging
import os
import re
import subprocess
from argparse import ArgumentParser, Namespace
from typing import List, Optional, Tuple, Set, Callable
from dataclasses import dataclass


CLANG_FORMAT = os.environ.get("CLANG_FORMAT", "clang-format")
PRETTIER = os.environ.get("PRETTIER", "node_modules/.bin/prettier")


def parse_args() -> Namespace:
parser = ArgumentParser()
parser.add_argument(
"--check",
help="only check for files requiring formatting; don't actually format them",
action="store_true",
default=False,
)
subparsers = parser.add_subparsers(dest="subcommand")
git_parser = subparsers.add_parser(
"git", help="Apply format to changes tracked by git"
)
git_parser.add_argument(
"--source",
help="consider files modified in the specified commit-ish; if not specified, defaults to all changes in the working directory",
type=str,
required=False,
default=None,
)
git_parser.add_argument(
"--target",
help="consider files modified since the specified commit-ish; defaults to HEAD",
type=str,
required=False,
default="HEAD",
)
git_parser.add_argument(
"--staged",
help="consider files with staged modifications only",
action="store_true",
default=False,
)
options = parser.parse_args()
if (
options.subcommand == "git"
and options.staged
and (options.source is not None or options.target != "HEAD")
):
logging.error(
"--staged cannot be used with --source or --target; use --staged with --source=HEAD"
)
exit(1)
return options


def check_clang_format() -> bool:
try:
# Run clang-format with --version to check its version
output = subprocess.check_output([CLANG_FORMAT, "--version"], encoding="utf-8")
major, _, _ = re.search(r"version\s*(\d+)\.(\d+)\.(\d+)", output).groups()
if int(major) < 18:
logging.error("clang-format version must be at least 18.0.0")
exit(1)
except FileNotFoundError:
# Clang-format is not in the PATH
logging.error("clang-format not found in the PATH")
exit(1)


def filter_files_by_exts(files: List[str], dir_path: str, exts: Tuple[str, ...]) -> List[str]:
return [file for file in files
if file.startswith(dir_path + "/") and file.endswith(exts)]


def clang_format(files: List[str], check: bool = False) -> bool:
if check:
result = subprocess.run(
[CLANG_FORMAT, "--verbose", "--dry-run", "--Werror"] + files)
return result.returncode == 0
else:
subprocess.run([CLANG_FORMAT, "--verbose", "-i"] + files)
return True


def prettier(files: List[str], check: bool = False) -> bool:
if check:
result = subprocess.run([PRETTIER, "--check"] + files)
return result.returncode == 0
else:
subprocess.run([PRETTIER, "--write"] + files)
return True


def git_get_modified_files(
target: str, source: Optional[str], staged: bool
) -> List[str]:
if staged:
files_in_diff = subprocess.check_output(
["git", "diff", "--diff-filter=d", "--name-only", "--cached"],
encoding="utf-8",
).splitlines()
return files_in_diff
else:
merge_base = subprocess.check_output(
["git", "merge-base", target, source or "HEAD"], encoding="utf-8"
).strip()
files_in_diff = subprocess.check_output(
["git", "diff", "--diff-filter=d", "--name-only", merge_base]
+ ([source] if source else []),
encoding="utf-8",
).splitlines()
return files_in_diff


def git_get_all_files() -> List[str]:
return subprocess.check_output(
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
encoding="utf-8"
).splitlines()


@dataclass
class FormatConfig:
directory: str
extensions: Tuple[str, ...]
formatter: Callable[[List[str], bool], bool]


FORMATTERS = [
# TODO: Re-enable once C++ PR is ready: https://github.com/cloudflare/workerd/pull/2505
#FormatConfig(
# directory='src/workerd',
# extensions=('.c++', '.h'),
# formatter=clang_format
#),
FormatConfig(
directory='src',
extensions=('.js', '.ts', '.cjs', '.ejs', '.mjs'),
formatter=prettier
),
FormatConfig(
directory='src',
extensions=('.json', ),
formatter=prettier
),
# TODO: lint bazel files
]


def format(config: FormatConfig, files: List[str], check: bool) -> bool:
matching_files = filter_files_by_exts(files, config.directory, config.extensions)

if not matching_files:
return True

return config.formatter(matching_files, check)


def main():
options = parse_args()
check_clang_format()
if options.subcommand == "git":
files = set(git_get_modified_files(
options.target, options.source, options.staged
))
else:
files = git_get_all_files()

all_ok = True

for config in FORMATTERS:
all_ok &= format(config, files, options.check)

if not all_ok:
logging.error("Code has linting issues. Fix with python ./tools/cross/format.py")
exit(1)

if __name__ == "__main__":
main()

0 comments on commit ab15158

Please sign in to comment.