diff --git a/.github/workflows/publish_wheel.yml b/.github/workflows/publish_wheel.yml new file mode 100644 index 00000000..27a07a41 --- /dev/null +++ b/.github/workflows/publish_wheel.yml @@ -0,0 +1,52 @@ +--- +name: Publish Wheel Package +on: + workflow_dispatch: + workflow_run: + workflows: [goreleaser] + types: + - completed + +jobs: + publish_whl: + name: Publish Wheel + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + with: + fetch-tags: true + - name: Run go release to create binaries + uses: actions/setup-go@v3 + with: + go-version: 1.21 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + distribution: goreleaser + version: latest + args: build --clean + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Install Twine + run: python -m pip install twine + - name: Pack WHEEL Package + working-directory: ./bdist/py + run: python3 setup.py + - name: Issue warning if TWINE_USERNAME and TWINE_PASSWORD are not set + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_AUTH_TOKEN }} + run: | + echo -n "::warning title=Missing authentication token::In order to publish an wheel package, you must set " + echo "the PYPI_USERNAME and PYPI_AUTH_TOKEN secrets" + if: ${{ (env.TWINE_USERNAME == '') || (env.TWINE_PASSWORD == '') }} + - name: Publish WHEEL package + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_AUTH_TOKEN }} + run: twine upload _wheel/* + working-directory: ./bdist/py + if: ${{ (env.TWINE_USERNAME == '') || (env.TWINE_PASSWORD == '') }} diff --git a/README.md b/README.md index e3269502..5ba05156 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,16 @@ You can add a `protolint` node to your `package.json` which may contain the cont If you want to get an output that matches the TSC compiler, use reporter `tsc`. +### Within Python projects + +You can use `protolint` as a linter within your python projects. Just add the desired version to +your `pyproject.toml` or `requirements.txt`. + +The wheels downloaded will contain the compiled go binaries for `protolint` and `protoc-gen-protolint`. Your platform must +be compatible with the supported binary platforms. + +You can add the linter configuration to the `tools.protolint` package in `pyproject.toml`. + ## Usage ```sh diff --git a/_testdata/py_project/project_with_yaml/protolint.yaml b/_testdata/py_project/project_with_yaml/protolint.yaml new file mode 100644 index 00000000..27d3df2b --- /dev/null +++ b/_testdata/py_project/project_with_yaml/protolint.yaml @@ -0,0 +1,6 @@ +--- +lint: + rules_option: + indent: + style: tab + newline: "\n" diff --git a/_testdata/py_project/project_with_yaml/pyproject.toml b/_testdata/py_project/project_with_yaml/pyproject.toml new file mode 100644 index 00000000..fa0b1df4 --- /dev/null +++ b/_testdata/py_project/project_with_yaml/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "protolint_user" +version = "0.1.0" + +[tools.protolint.rules_option.indent] +style = "\t" +newline = "\r\n" \ No newline at end of file diff --git a/_testdata/py_project/project_with_yaml_parent/child/pyproject.toml b/_testdata/py_project/project_with_yaml_parent/child/pyproject.toml new file mode 100644 index 00000000..fa0b1df4 --- /dev/null +++ b/_testdata/py_project/project_with_yaml_parent/child/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "protolint_user" +version = "0.1.0" + +[tools.protolint.rules_option.indent] +style = "\t" +newline = "\r\n" \ No newline at end of file diff --git a/_testdata/py_project/project_with_yaml_parent/protolint.yaml b/_testdata/py_project/project_with_yaml_parent/protolint.yaml new file mode 100644 index 00000000..27d3df2b --- /dev/null +++ b/_testdata/py_project/project_with_yaml_parent/protolint.yaml @@ -0,0 +1,6 @@ +--- +lint: + rules_option: + indent: + style: tab + newline: "\n" diff --git a/_testdata/py_project/pyproject/pyproject.toml b/_testdata/py_project/pyproject/pyproject.toml new file mode 100644 index 00000000..1b5938f1 --- /dev/null +++ b/_testdata/py_project/pyproject/pyproject.toml @@ -0,0 +1,3 @@ +[tools.protolint.rules_option.indent] +style = "\t" +newline = "\n" \ No newline at end of file diff --git a/_testdata/py_project/pyproject_no_protolint/pyproject.toml b/_testdata/py_project/pyproject_no_protolint/pyproject.toml new file mode 100644 index 00000000..131027b6 --- /dev/null +++ b/_testdata/py_project/pyproject_no_protolint/pyproject.toml @@ -0,0 +1,6 @@ +[project] +name = "protolint_user" +version = "0.1.0" + +[tools] +y = "x" diff --git a/_testdata/py_project/pyproject_no_tools/pyproject.toml b/_testdata/py_project/pyproject_no_tools/pyproject.toml new file mode 100644 index 00000000..9ff3d242 --- /dev/null +++ b/_testdata/py_project/pyproject_no_tools/pyproject.toml @@ -0,0 +1,3 @@ +[project] +name = "protolint_user" +version = "0.1.0" diff --git a/_testdata/py_project/with_pyproject/pyproject.toml b/_testdata/py_project/with_pyproject/pyproject.toml new file mode 100644 index 00000000..0f11862e --- /dev/null +++ b/_testdata/py_project/with_pyproject/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "protolint_user" +version = "0.1.0" + +[tools.protolint.rules_option.indent] +style = "\t" +newline = "\n" \ No newline at end of file diff --git a/bdist/py/.gitignore b/bdist/py/.gitignore new file mode 100644 index 00000000..f9446ebe --- /dev/null +++ b/bdist/py/.gitignore @@ -0,0 +1,2 @@ +_bdist/* +_wheel diff --git a/bdist/py/setup.py b/bdist/py/setup.py new file mode 100755 index 00000000..ec7a4c50 --- /dev/null +++ b/bdist/py/setup.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import contextlib +import hashlib +import logging +import os +import pathlib +import shutil +import subprocess +import sys +import zipfile + + +@contextlib.contextmanager +def as_cwd(path): + """Changes working directory and returns to previous on exit.""" + prev_cwd = pathlib.Path.cwd() + os.chdir(path) + try: + yield + finally: + os.chdir(prev_cwd) + + +def clear_dir(path): + if path.is_dir(): + for e in path.glob("**/*"): + if e.is_dir(): + logger.debug("Clearing entries from %s", e) + clear_dir(e) + else: + logger.debug("Removing file %s", e) + e.unlink() + logger.debug("Removing directory %s", path) + path.rmdir() + + +logger = logging.getLogger("BUILD") +logger.setLevel(logging.INFO) +logger.addHandler(logging.StreamHandler(sys.stdout)) + +file_dir: pathlib.Path = pathlib.Path(os.path.dirname(__file__)) +bdist = file_dir / "_bdist" +wheel = file_dir / "_wheel" + +if wheel.is_dir(): + clear_dir(wheel) + +bdist.mkdir(exist_ok=True, parents=True) +wheel.mkdir(exist_ok=True, parents=True) + +logger.info("Building files from %s", file_dir) +repo_root = file_dir / ".." / ".." +license_file = repo_root / "LICENSE" +readme_md = repo_root / "README.md" + +logger.info("Using repository root %s", repo_root) + +dist = repo_root / "dist" + +logger.info("Using previously files from %s", dist) + +cp: subprocess.CompletedProcess = subprocess.run(["git", "describe", "--tag"], capture_output=True) +version_id = cp.stdout.decode("utf8").lstrip("v").rstrip("\n") +del cp + +logger.info("Assuming version is %s", version_id) + +ap_map: dict[str, str] = { + "darwin_amd64_v1": "macosx_x86_64", + "darwin_arm64": "darwin_aarch64", + "linux_amd64_v1": "manylinux1_x86_64", + "linux_arm64": "manylinux1_aarch64", + "linux_arm_7": "linux_armv7l", + "windows_amd64_v1": "win_amd64", + "windows_arm64": "win_arch64", +} + +executables = {"protolint", "protoc-gen-protolint"} + +PY_TAG = "py2.py3" +ABI_TAG = "none" + +for arch_platform in ap_map.keys(): + tag = f"{PY_TAG}-{ABI_TAG}-{ap_map[arch_platform]}" + logger.info("Packing files for %s using tag %s", arch_platform, tag) + suffix = ".exe" if "windows" in arch_platform else "" + + pdir = bdist / arch_platform + clear_dir(pdir) + pdir.mkdir(exist_ok=True, parents=True) + + p_executables = [dist / f"{exe}_{arch_platform}" / f"{exe}{suffix}" for exe in executables] + + logger.debug("Creating wheel data folder") + dataFolder = pdir / f"protolint-{version_id}.data" + logger.debug("Creating wheel data folder") + distInfoFolder = pdir / f"protolint-{version_id}.dist-info" + + dataFolder.mkdir(parents=True, exist_ok=True) + distInfoFolder.mkdir(parents=True, exist_ok=True) + + with as_cwd(pdir): + logger.debug("Creating scripts folder") + scripts = dataFolder / "scripts" + scripts.mkdir(parents=True, exist_ok=True) + for p in p_executables: + logger.debug("Copying executable %s to scripts folder %s", p, scripts) + shutil.copy(p, scripts) + + logger.debug("Copying LICENSE from %s", license_file) + shutil.copy(license_file, distInfoFolder) + + with (distInfoFolder / "WHEEL").open("w+") as wl: + logger.debug("Writing WHEEL file") + wl.writelines([ + "Wheel-Version: 1.0\n", + "Generator: https://github.com/yoheimuta/protolint/\n", + "Root-Is-PureLib: false\n", + f"Tag: {tag}\n"] + ) + + with (distInfoFolder / "METADATA").open("w+") as ml: + logger.debug("Writing METADATA file") + ml.writelines([ + "Metadata-Version: 2.1\n", + "Name: protolint\n", + "Summary: A pluggable linter and fixer to enforce Protocol Buffer style and conventions.\n", + "Description-Content-Type: text/markdown\n", + "Author: yohei yoshimuta\n", + "Maintainer: yohei yoshimuta\n", + "Home-page: https://github.com/yoheimuta/protolint/\n", + "License-File: LICENSE\n", + "License: MIT\n", + "Classifier: Development Status :: 5 - Production/Stable\n", + "Classifier: Environment :: Console\n", + "Classifier: Intended Audience :: Developers\n", + "Classifier: License :: OSI Approved :: MIT License\n", + "Classifier: Natural Language :: English\n", + "Classifier: Operating System :: MacOS\n", + "Classifier: Operating System :: Microsoft :: Windows\n", + "Classifier: Operating System :: POSIX :: Linux\n", + "Classifier: Programming Language :: Go\n", + "Classifier: Topic :: Software Development :: Pre-processors\n", + "Classifier: Topic :: Utilities\n", + "Project-URL: Official Website, https://github.com/yoheimuta/protolint/\n", + "Project-URL: Source Code, https://github.com/yoheimuta/protolint.git\n", + "Project-URL: Issue Tracker, https://github.com/yoheimuta/protolint/issues\n", + f"Version: {version_id} \n", + f"Download-URL: https://github.com/yoheimuta/protolint/releases/tag/v{version_id}/\n", + ]) + + with readme_md.open("r") as readme: + ml.writelines(readme.readlines()) + + wheel_content = list(distInfoFolder.glob("**/*")) + list(dataFolder.glob("**/*")) + elements_to_relative_paths = {entry: str(entry).lstrip(str(pdir)).lstrip("/").lstrip("\\") for entry in wheel_content if entry.is_file()} + with (distInfoFolder / "RECORD").open("w+") as rl: + logger.debug("Writing RECORD file") + for entry in elements_to_relative_paths.keys(): + relPath = elements_to_relative_paths[entry] + sha256 = hashlib.sha256(entry.read_bytes()) + fs = entry.stat().st_size + rl.write(f"{relPath},sha256={sha256.hexdigest()},{str(fs)}\n") + + rl.write(distInfoFolder.name + "/RECORD,,\n") + wheel_content.append(distInfoFolder / "RECORD") + + whl_file = wheel / f"protolint-{version_id}-{tag}.whl" + if whl_file.is_file(): + logger.debug("Removing existing wheel file") + whl_file.unlink() + + with zipfile.ZipFile(whl_file, "w", compression=zipfile.ZIP_DEFLATED) as whl: + logger.info("Creating python wheel %s", whl_file) + for content in wheel_content: + whl.write( + content, + content.relative_to(pdir), + zipfile.ZIP_DEFLATED, + ) + +logger.info("Done") diff --git a/go.mod b/go.mod index cf041a54..d98243e2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/yoheimuta/protolint require ( + github.com/BurntSushi/toml v1.3.2 + github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab github.com/gertd/go-pluralize v0.2.0 github.com/golang/protobuf v1.5.2 github.com/hashicorp/go-hclog v1.2.0 @@ -12,7 +14,6 @@ require ( ) require ( - github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab // indirect github.com/fatih/color v1.7.0 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/mattn/go-colorable v0.1.4 // indirect diff --git a/go.sum b/go.sum index 0e70b3df..eb3ba696 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8 h1:W9o46d2kbNL06lq7UNDPV0zYLzkrde/bjIqO02eoll0= -github.com/chavacava/garif v0.0.0-20230227094218-b8c73b2037b8/go.mod h1:gakxgyXaaPkxvLw1XQxNGK4I37ys9iBRzNUx/B7pUCo= github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab h1:5JxePczlyGAtj6R1MUEFZ/UFud6FfsOejq7xLC2ZIb0= github.com/chavacava/garif v0.0.0-20230608123814-4bd63c2919ab/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -87,12 +87,9 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yoheimuta/go-protoparser/v4 v4.7.0 h1:80LGfVM25sCoNDD08hv9O0ShQMjoTrIE76j5ON+gq3U= @@ -177,7 +174,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/linter/config/customizableSeverityOption.go b/internal/linter/config/customizableSeverityOption.go index ef7d35b6..eb5fd2cc 100644 --- a/internal/linter/config/customizableSeverityOption.go +++ b/internal/linter/config/customizableSeverityOption.go @@ -5,7 +5,7 @@ import "github.com/yoheimuta/protolint/linter/rule" // CustomizableSeverityOption represents an option where the // severity of a rule can be configured via yaml. type CustomizableSeverityOption struct { - severity *rule.Severity `yaml:"severity"` + severity *rule.Severity `yaml:"severity" toml:"severity"` } // Severity returns the configured severity. If no severity diff --git a/internal/linter/config/directories.go b/internal/linter/config/directories.go index dffddfea..63071994 100644 --- a/internal/linter/config/directories.go +++ b/internal/linter/config/directories.go @@ -8,7 +8,7 @@ import ( // Directories represents the target directories. type Directories struct { - Exclude []string `yaml:"exclude"` + Exclude []string `yaml:"exclude" json:"exclude" toml:"exclude"` } func (d Directories) shouldSkipRule( diff --git a/internal/linter/config/enumFieldNamesZeroValueEndWithOption.go b/internal/linter/config/enumFieldNamesZeroValueEndWithOption.go index 78709212..9309898d 100644 --- a/internal/linter/config/enumFieldNamesZeroValueEndWithOption.go +++ b/internal/linter/config/enumFieldNamesZeroValueEndWithOption.go @@ -3,5 +3,5 @@ package config // EnumFieldNamesZeroValueEndWithOption represents the option for the ENUM_FIELD_NAMES_ZERO_VALUE_END_WITH rule. type EnumFieldNamesZeroValueEndWithOption struct { CustomizableSeverityOption - Suffix string `yaml:"suffix"` + Suffix string `yaml:"suffix" json:"suffix" toml:"suffix"` } diff --git a/internal/linter/config/enumFieldsHaveCommentOption.go b/internal/linter/config/enumFieldsHaveCommentOption.go index 6046816c..718aa77a 100644 --- a/internal/linter/config/enumFieldsHaveCommentOption.go +++ b/internal/linter/config/enumFieldsHaveCommentOption.go @@ -3,5 +3,5 @@ package config // EnumFieldsHaveCommentOption represents the option for the ENUM_FIELDS_HAVE_COMMENT rule. type EnumFieldsHaveCommentOption struct { CustomizableSeverityOption - ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style"` + ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style" json:"should_follow_golang_style" toml:"should_follow_golang_style"` } diff --git a/internal/linter/config/enumsHaveCommentOption.go b/internal/linter/config/enumsHaveCommentOption.go index 8b1085ff..f2861a9c 100644 --- a/internal/linter/config/enumsHaveCommentOption.go +++ b/internal/linter/config/enumsHaveCommentOption.go @@ -3,5 +3,5 @@ package config // EnumsHaveCommentOption represents the option for the ENUMS_HAVE_COMMENT rule. type EnumsHaveCommentOption struct { CustomizableSeverityOption - ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style"` + ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style" json:"should_follow_golang_style" toml:"should_follow_golang_style"` } diff --git a/internal/linter/config/externalConfig.go b/internal/linter/config/externalConfig.go index 53aa64d0..61686098 100644 --- a/internal/linter/config/externalConfig.go +++ b/internal/linter/config/externalConfig.go @@ -6,11 +6,7 @@ type Lint struct { Files Files Directories Directories Rules Rules - RulesOption RulesOption `yaml:"rules_option" json:"rules_option"` -} - -type embeddedConfig struct { - Protolint *Lint `json:"protolint"` + RulesOption RulesOption `yaml:"rules_option" json:"rules_option" toml:"rules_option"` } // ExternalConfig represents the external configuration. @@ -31,13 +27,3 @@ func (c ExternalConfig) ShouldSkipRule( lint.Directories.shouldSkipRule(displayPath) || lint.Rules.shouldSkipRule(ruleID, defaultRuleIDs) } - -func (p embeddedConfig) toExternalConfig() *ExternalConfig { - if p.Protolint == nil { - return nil - } - - return &ExternalConfig{ - Lint: *p.Protolint, - } -} diff --git a/internal/linter/config/externalConfigProvider.go b/internal/linter/config/externalConfigProvider.go index 69abbe6e..a6a147c5 100644 --- a/internal/linter/config/externalConfigProvider.go +++ b/internal/linter/config/externalConfigProvider.go @@ -52,6 +52,9 @@ func getLoaderFromExtension(filePath string) (configLoader, error) { if strings.HasSuffix(filePath, packageJsonFileNameForJsExtension) { return jsonConfigLoader{filePath: filePath}, nil } + if strings.HasSuffix(filePath, pyProjectTomlFileNameForPyExtension) { + return tomlConfigLoader{filePath: filePath}, nil + } return nil, fmt.Errorf("%s is not a valid support file extension", filePath) } @@ -115,5 +118,18 @@ func getExternalConfigLoader( return jsonConfigLoader{filePath: filePath}, nil } + // after checking for protolint yaml and npm.json files, go for pyproject.toml of python + for _, dir := range dirPaths { + filePath := filepath.Join(dir, pyProjectTomlFileNameForPy) + checkedPaths = append(checkedPaths, filePath) + if _, err := os.Stat(filePath); err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + return tomlConfigLoader{filePath: filePath}, nil + } + return nil, fmt.Errorf("not found config file by searching `%s`", strings.Join(checkedPaths, ",")) } diff --git a/internal/linter/config/externalConfigProvider_test.go b/internal/linter/config/externalConfigProvider_test.go index cd5961ba..447e82b2 100644 --- a/internal/linter/config/externalConfigProvider_test.go +++ b/internal/linter/config/externalConfigProvider_test.go @@ -49,10 +49,10 @@ func TestGetExternalConfig(t *testing.T) { }, }, Rules: struct { - NoDefault bool `yaml:"no_default"` - AllDefault bool `yaml:"all_default"` - Add []string `yaml:"add"` - Remove []string `yaml:"remove"` + NoDefault bool `yaml:"no_default" json:"no_default" toml:"no_default"` + AllDefault bool `yaml:"all_default" json:"all_default" toml:"all_default"` + Add []string `yaml:"add" json:"add" toml:"add"` + Remove []string `yaml:"remove" json:"remove" toml:"remove"` }{ NoDefault: true, Add: []string{ @@ -226,6 +226,16 @@ func TestGetExternalConfig(t *testing.T) { cwdPath: setting_test.TestDataPath("js_config", "package_no_protolint"), wantExternalConfig: nil, }, + { + name: "found a pyproject.toml without 'tools.protolint' in it", + cwdPath: setting_test.TestDataPath("py_project", "pyproject_no_protolint"), + wantExternalConfig: nil, + }, + { + name: "found a pyproject.toml without 'tools' in it", + cwdPath: setting_test.TestDataPath("py_project", "pyproject_no_tools"), + wantExternalConfig: nil, + }, { name: "found a package.json with 'protolint' and other stuff in it", cwdPath: setting_test.TestDataPath("js_config", "non_pure_package"), @@ -241,6 +251,21 @@ func TestGetExternalConfig(t *testing.T) { }, }, }, + { + name: "found a pyproject.toml with 'tools.protolint' and other stuff in it", + cwdPath: setting_test.TestDataPath("py_project", "with_pyproject"), + wantExternalConfig: &config.ExternalConfig{ + SourcePath: "pyproject.toml", + Lint: config.Lint{ + RulesOption: config.RulesOption{ + Indent: config.IndentOption{ + Style: "\t", + Newline: "\n", + }, + }, + }, + }, + }, { name: "found a package.json with 'protolint' and other stuff in it, but superseded by sibling protolint.yaml", cwdPath: setting_test.TestDataPath("js_config", "package_with_yaml"), @@ -256,6 +281,21 @@ func TestGetExternalConfig(t *testing.T) { }, }, }, + { + name: "found a pyproject.toml with 'toolsprotolint' and other stuff in it, but superseded by sibling protolint.yaml", + cwdPath: setting_test.TestDataPath("py_project", "project_with_yaml"), + wantExternalConfig: &config.ExternalConfig{ + SourcePath: "protolint.yaml", + Lint: config.Lint{ + RulesOption: config.RulesOption{ + Indent: config.IndentOption{ + Style: "\t", + Newline: "\n", + }, + }, + }, + }, + }, { name: "found a package.json with 'protolint' and other stuff in it, but superseded by parent protolint.yaml", cwdPath: setting_test.TestDataPath("js_config", "package_with_yaml_parent", "child"), @@ -271,6 +311,21 @@ func TestGetExternalConfig(t *testing.T) { }, }, }, + { + name: "found a pyproject.toml with 'toolsprotolint' and other stuff in it, but superseded by parent protolint.yaml", + cwdPath: setting_test.TestDataPath("py_project", "project_with_yaml_parent", "child"), + wantExternalConfig: &config.ExternalConfig{ + SourcePath: setting_test.TestDataPath("py_project", "project_with_yaml_parent", "protolint.yaml"), + Lint: config.Lint{ + RulesOption: config.RulesOption{ + Indent: config.IndentOption{ + Style: "\t", + Newline: "\n", + }, + }, + }, + }, + }, } { test := test t.Run(test.name, func(t *testing.T) { diff --git a/internal/linter/config/externalConfig_test.go b/internal/linter/config/externalConfig_test.go index a86a623d..baba388c 100644 --- a/internal/linter/config/externalConfig_test.go +++ b/internal/linter/config/externalConfig_test.go @@ -44,10 +44,10 @@ func TestExternalConfig_ShouldSkipRule(t *testing.T) { }, }, Rules: struct { - NoDefault bool `yaml:"no_default"` - AllDefault bool `yaml:"all_default"` - Add []string `yaml:"add"` - Remove []string `yaml:"remove"` + NoDefault bool `yaml:"no_default" json:"no_default" toml:"no_default"` + AllDefault bool `yaml:"all_default" json:"all_default" toml:"all_default"` + Add []string `yaml:"add" json:"add" toml:"add"` + Remove []string `yaml:"remove" json:"remove" toml:"remove"` }{ NoDefault: true, Add: []string{ @@ -81,10 +81,10 @@ func TestExternalConfig_ShouldSkipRule(t *testing.T) { }, }, Rules: struct { - NoDefault bool `yaml:"no_default"` - AllDefault bool `yaml:"all_default"` - Add []string `yaml:"add"` - Remove []string `yaml:"remove"` + NoDefault bool `yaml:"no_default" json:"no_default" toml:"no_default"` + AllDefault bool `yaml:"all_default" json:"all_default" toml:"all_default"` + Add []string `yaml:"add" json:"add" toml:"add"` + Remove []string `yaml:"remove" json:"remove" toml:"remove"` }{ NoDefault: false, Add: []string{ diff --git a/internal/linter/config/fieldNamesExcludePrepositionsOption.go b/internal/linter/config/fieldNamesExcludePrepositionsOption.go index 64f5c88d..3ea82a4a 100644 --- a/internal/linter/config/fieldNamesExcludePrepositionsOption.go +++ b/internal/linter/config/fieldNamesExcludePrepositionsOption.go @@ -3,6 +3,6 @@ package config // FieldNamesExcludePrepositionsOption represents the option for the FIELD_NAMES_EXCLUDE_PREPOSITIONS rule. type FieldNamesExcludePrepositionsOption struct { CustomizableSeverityOption - Prepositions []string `yaml:"prepositions"` - Excludes []string `yaml:"excludes"` + Prepositions []string `yaml:"prepositions" json:"prepositions" toml:"prepositions"` + Excludes []string `yaml:"excludes" json:"excludes" toml:"excludes"` } diff --git a/internal/linter/config/fieldsHaveCommentOption.go b/internal/linter/config/fieldsHaveCommentOption.go index bd6811bd..46949431 100644 --- a/internal/linter/config/fieldsHaveCommentOption.go +++ b/internal/linter/config/fieldsHaveCommentOption.go @@ -3,5 +3,5 @@ package config // FieldsHaveCommentOption represents the option for the FIELDS_HAVE_COMMENT rule. type FieldsHaveCommentOption struct { CustomizableSeverityOption - ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style"` + ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style" json:"should_follow_golang_style" toml:"should_follow_golang_style"` } diff --git a/internal/linter/config/fileNamesLowerSnakeCaseOption.go b/internal/linter/config/fileNamesLowerSnakeCaseOption.go index f56ab92b..6af5212d 100644 --- a/internal/linter/config/fileNamesLowerSnakeCaseOption.go +++ b/internal/linter/config/fileNamesLowerSnakeCaseOption.go @@ -3,5 +3,5 @@ package config // FileNamesLowerSnakeCaseOption represents the option for the FILE_NAMES_LOWER_SNAKE_CASE rule. type FileNamesLowerSnakeCaseOption struct { CustomizableSeverityOption - Excludes []string `yaml:"excludes"` + Excludes []string `yaml:"excludes" json:"excludes" toml:"excludes"` } diff --git a/internal/linter/config/files.go b/internal/linter/config/files.go index b8cfb0ba..5c2eda38 100644 --- a/internal/linter/config/files.go +++ b/internal/linter/config/files.go @@ -4,7 +4,7 @@ import "github.com/yoheimuta/protolint/internal/stringsutil" // Files represents the target files. type Files struct { - Exclude []string `yaml:"exclude"` + Exclude []string `yaml:"exclude" json:"exclude" toml:"exclude"` } func (d Files) shouldSkipRule( diff --git a/internal/linter/config/ignore.go b/internal/linter/config/ignore.go index 24801d8d..84922f50 100644 --- a/internal/linter/config/ignore.go +++ b/internal/linter/config/ignore.go @@ -4,8 +4,8 @@ import "github.com/yoheimuta/protolint/internal/stringsutil" // Ignore represents files ignoring the specific rule. type Ignore struct { - ID string `yaml:"id"` - Files []string `yaml:"files"` + ID string `yaml:"id" json:"id" toml:"id"` + Files []string `yaml:"files" json:"files" toml:"files"` } func (i Ignore) shouldSkipRule( diff --git a/internal/linter/config/importsSortedOption.go b/internal/linter/config/importsSortedOption.go index 1a64583d..4acffcbc 100644 --- a/internal/linter/config/importsSortedOption.go +++ b/internal/linter/config/importsSortedOption.go @@ -28,3 +28,21 @@ func (i *ImportsSortedOption) UnmarshalYAML(unmarshal func(interface{}) error) e } return nil } + +// UnmarshalTOML implements toml Unmarshaler interface. +func (i *ImportsSortedOption) UnmarshalTOML(data interface{}) error { + optionsMap := map[string]interface{}{} + for k, v := range data.(map[string]interface{}) { + optionsMap[k] = v.(string) + } + + if newline, ok := optionsMap["newline"]; ok { + switch newline.(string) { + case "\n", "\r", "\r\n", "": + i.Newline = newline.(string) + default: + return fmt.Errorf(`%s is an invalid newline option. valid option is \n, \r or \r\n`, newline) + } + } + return nil +} diff --git a/internal/linter/config/indentOption.go b/internal/linter/config/indentOption.go index 867eaf2d..d8c2dc52 100644 --- a/internal/linter/config/indentOption.go +++ b/internal/linter/config/indentOption.go @@ -49,3 +49,46 @@ func (i *IndentOption) UnmarshalYAML(unmarshal func(interface{}) error) error { i.NotInsertNewline = option.NotInsertNewline return nil } + +// UnmarshalTOML implements toml Unmarshaler interface. +func (i *IndentOption) UnmarshalTOML(data interface{}) error { + optionsMap := map[string]interface{}{} + for k, v := range data.(map[string]interface{}) { + optionsMap[k] = v.(string) + } + + if style, ok := optionsMap["style"]; ok { + styleStr := style.(string) + switch styleStr { + case "\t": + styleStr = "\t" + case "tab": + styleStr = "\t" + case "4": + styleStr = strings.Repeat(" ", 4) + case "2": + styleStr = strings.Repeat(" ", 2) + case "": + break + default: + return fmt.Errorf("%s is an invalid style option. valid option is \\t, tab, 4 or 2", style) + } + i.Style = styleStr + } + + if newLine, ok := optionsMap["newline"]; ok { + newLineStr := newLine.(string) + switch newLineStr { + case "\n", "\r", "\r\n", "": + i.Newline = newLineStr + default: + return fmt.Errorf(`%s is an invalid newline option. valid option is \n, \r or \r\n`, newLine) + } + } + + if insertNoNewLine, ok := optionsMap["not_insert_newline"]; ok { + i.NotInsertNewline = insertNoNewLine.(bool) + } + + return nil +} diff --git a/internal/linter/config/jsonExternalConfigProvider.go b/internal/linter/config/jsonExternalConfigProvider.go index 72ba7047..c365c9f1 100644 --- a/internal/linter/config/jsonExternalConfigProvider.go +++ b/internal/linter/config/jsonExternalConfigProvider.go @@ -18,7 +18,7 @@ func (j jsonConfigLoader) LoadExternalConfig() (*ExternalConfig, error) { } var config ExternalConfig - var jsonData embeddedConfig + var jsonData jsonEmbeddedConfig // do not unmarshal strict. JS specific package.json will contain // other values as well. if jsonErr := json.Unmarshal(data, &jsonData); jsonErr != nil { @@ -35,3 +35,17 @@ func (j jsonConfigLoader) LoadExternalConfig() (*ExternalConfig, error) { return &config, nil } + +type jsonEmbeddedConfig struct { + Protolint *Lint `json:"protolint"` +} + +func (p jsonEmbeddedConfig) toExternalConfig() *ExternalConfig { + if p.Protolint == nil { + return nil + } + + return &ExternalConfig{ + Lint: *p.Protolint, + } +} diff --git a/internal/linter/config/maxLineLengthOption.go b/internal/linter/config/maxLineLengthOption.go index 1b9a319f..1b110249 100644 --- a/internal/linter/config/maxLineLengthOption.go +++ b/internal/linter/config/maxLineLengthOption.go @@ -3,6 +3,6 @@ package config // MaxLineLengthOption represents the option for the MAX_LINE_LENGTH rule. type MaxLineLengthOption struct { CustomizableSeverityOption - MaxChars int `yaml:"max_chars"` - TabChars int `yaml:"tab_chars"` + MaxChars int `yaml:"max_chars" json:"max_chars" toml:"max_chars"` + TabChars int `yaml:"tab_chars" json:"tab_chars" toml:"tab_chars"` } diff --git a/internal/linter/config/messageNamesExcludePrepositionsOption.go b/internal/linter/config/messageNamesExcludePrepositionsOption.go index 2dc644f4..3a016cad 100644 --- a/internal/linter/config/messageNamesExcludePrepositionsOption.go +++ b/internal/linter/config/messageNamesExcludePrepositionsOption.go @@ -3,6 +3,6 @@ package config // MessageNamesExcludePrepositionsOption represents the option for the MESSAGE_NAMES_EXCLUDE_PREPOSITIONS rule. type MessageNamesExcludePrepositionsOption struct { CustomizableSeverityOption - Prepositions []string `yaml:"prepositions"` - Excludes []string `yaml:"excludes"` + Prepositions []string `yaml:"prepositions" json:"prepositions" toml:"prepositions"` + Excludes []string `yaml:"excludes" json:"excludes" toml:"excludes"` } diff --git a/internal/linter/config/messagesHaveCommentOption.go b/internal/linter/config/messagesHaveCommentOption.go index 5088bfba..6c3879b8 100644 --- a/internal/linter/config/messagesHaveCommentOption.go +++ b/internal/linter/config/messagesHaveCommentOption.go @@ -3,5 +3,5 @@ package config // MessagesHaveCommentOption represents the option for the MESSAGES_HAVE_COMMENT rule. type MessagesHaveCommentOption struct { CustomizableSeverityOption - ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style"` + ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style" json:"should_follow_golang_style" toml:"should_follow_golang_style"` } diff --git a/internal/linter/config/quoteConsistentOption.go b/internal/linter/config/quoteConsistentOption.go index b391d77d..d675e4a3 100644 --- a/internal/linter/config/quoteConsistentOption.go +++ b/internal/linter/config/quoteConsistentOption.go @@ -47,3 +47,31 @@ func (r *QuoteConsistentOption) UnmarshalYAML(unmarshal func(interface{}) error) } return nil } + +func (r *QuoteConsistentOption) UnmarshalToml(data interface{}) error { + optionsMap := map[string]interface{}{} + for k, v := range data.(map[string]interface{}) { + optionsMap[k] = v.(string) + } + + if quote, ok := optionsMap["quote"]; ok { + quoteStr := quote.(string) + if 0 < len(quoteStr) { + supportQuotes := map[string]QuoteType{ + "double": DoubleQuote, + "single": SingleQuote, + } + quote, ok := supportQuotes[quoteStr] + if !ok { + var list []string + for k := range supportQuotes { + list = append(list, k) + } + return fmt.Errorf("%s is an invalid quote. valid options are [%s]", + quoteStr, strings.Join(list, ",")) + } + r.Quote = quote + } + } + return nil +} diff --git a/internal/linter/config/repeatedFieldNamesPluralizedOption.go b/internal/linter/config/repeatedFieldNamesPluralizedOption.go index 89d8a595..24d3463b 100644 --- a/internal/linter/config/repeatedFieldNamesPluralizedOption.go +++ b/internal/linter/config/repeatedFieldNamesPluralizedOption.go @@ -3,8 +3,8 @@ package config // RepeatedFieldNamesPluralizedOption represents the option for the REPEATED_FIELD_NAMES_PLURALIZED rule. type RepeatedFieldNamesPluralizedOption struct { CustomizableSeverityOption - PluralRules map[string]string `yaml:"plural_rules"` - SingularRules map[string]string `yaml:"singular_rules"` - UncountableRules []string `yaml:"uncountable_rules"` - IrregularRules map[string]string `yaml:"irregular_rules"` + PluralRules map[string]string `yaml:"plural_rules" json:"plural_rules" toml:"plural_rules"` + SingularRules map[string]string `yaml:"singular_rules" json:"singular_rules" toml:"singular_rules"` + UncountableRules []string `yaml:"uncountable_rules" json:"uncountable_rules" toml:"uncountable_rules"` + IrregularRules map[string]string `yaml:"irregular_rules" json:"irregular_rules" toml:"irregular_rules"` } diff --git a/internal/linter/config/rpcNamesCaseOption.go b/internal/linter/config/rpcNamesCaseOption.go index f10590fd..cf570e99 100644 --- a/internal/linter/config/rpcNamesCaseOption.go +++ b/internal/linter/config/rpcNamesCaseOption.go @@ -49,3 +49,34 @@ func (r *RPCNamesCaseOption) UnmarshalYAML(unmarshal func(interface{}) error) er } return nil } + +// UnmarshalTOML implements toml Unmarshaler interface. +func (r *RPCNamesCaseOption) UnmarshalTOML(data interface{}) error { + optionsMap := map[string]interface{}{} + for k, v := range data.(map[string]interface{}) { + optionsMap[k] = v.(string) + } + + if convention, ok := optionsMap["convention"]; ok { + conventionStr := convention.(string) + if 0 < len(conventionStr) { + supportConventions := map[string]ConventionType{ + "lower_camel_case": ConventionLowerCamel, + "upper_snake_case": ConventionUpperSnake, + "lower_snake_case": ConventionLowerSnake, + } + convention, ok := supportConventions[conventionStr] + if !ok { + var list []string + for k := range supportConventions { + list = append(list, k) + } + return fmt.Errorf("%s is an invalid name convention. valid options are [%s]", + conventionStr, strings.Join(list, ",")) + } + r.Convention = convention + } + } + + return nil +} diff --git a/internal/linter/config/rpcsHaveCommentOption.go b/internal/linter/config/rpcsHaveCommentOption.go index d37b34af..9be73452 100644 --- a/internal/linter/config/rpcsHaveCommentOption.go +++ b/internal/linter/config/rpcsHaveCommentOption.go @@ -3,5 +3,5 @@ package config // RPCsHaveCommentOption represents the option for the RPCS_HAVE_COMMENT rule. type RPCsHaveCommentOption struct { CustomizableSeverityOption - ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style"` + ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style" json:"should_follow_golang_style" toml:"should_follow_golang_style"` } diff --git a/internal/linter/config/rules.go b/internal/linter/config/rules.go index e9a5cb44..5f6f7dc2 100644 --- a/internal/linter/config/rules.go +++ b/internal/linter/config/rules.go @@ -4,10 +4,10 @@ import "github.com/yoheimuta/protolint/internal/stringsutil" // Rules represents the enabled rule set. type Rules struct { - NoDefault bool `yaml:"no_default"` - AllDefault bool `yaml:"all_default"` - Add []string `yaml:"add"` - Remove []string `yaml:"remove"` + NoDefault bool `yaml:"no_default" json:"no_default" toml:"no_default"` + AllDefault bool `yaml:"all_default" json:"all_default" toml:"all_default"` + Add []string `yaml:"add" json:"add" toml:"add"` + Remove []string `yaml:"remove" json:"remove" toml:"remove"` } func (r Rules) shouldSkipRule( diff --git a/internal/linter/config/rulesOption.go b/internal/linter/config/rulesOption.go index b5203b20..c4999776 100644 --- a/internal/linter/config/rulesOption.go +++ b/internal/linter/config/rulesOption.go @@ -2,34 +2,34 @@ package config // RulesOption represents the option for some rules. type RulesOption struct { - FileNamesLowerSnakeCase FileNamesLowerSnakeCaseOption `yaml:"file_names_lower_snake_case"` - QuoteConsistentOption QuoteConsistentOption `yaml:"quote_consistent"` - ImportsSorted ImportsSortedOption `yaml:"imports_sorted"` - MaxLineLength MaxLineLengthOption `yaml:"max_line_length"` - Indent IndentOption `yaml:"indent"` - EnumFieldNamesZeroValueEndWith EnumFieldNamesZeroValueEndWithOption `yaml:"enum_field_names_zero_value_end_with"` - ServiceNamesEndWith ServiceNamesEndWithOption `yaml:"service_names_end_with"` - FieldNamesExcludePrepositions FieldNamesExcludePrepositionsOption `yaml:"field_names_exclude_prepositions"` - MessageNamesExcludePrepositions MessageNamesExcludePrepositionsOption `yaml:"message_names_exclude_prepositions"` - RPCNamesCaseOption RPCNamesCaseOption `yaml:"rpc_names_case"` - MessagesHaveComment MessagesHaveCommentOption `yaml:"messages_have_comment"` - ServicesHaveComment ServicesHaveCommentOption `yaml:"services_have_comment"` - RPCsHaveComment RPCsHaveCommentOption `yaml:"rpcs_have_comment"` - FieldsHaveComment FieldsHaveCommentOption `yaml:"fields_have_comment"` - EnumsHaveComment EnumsHaveCommentOption `yaml:"enums_have_comment"` - EnumFieldsHaveComment EnumFieldsHaveCommentOption `yaml:"enum_fields_have_comment"` - SyntaxConsistent SyntaxConsistentOption `yaml:"syntax_consistent"` - RepeatedFieldNamesPluralized RepeatedFieldNamesPluralizedOption `yaml:"repeated_field_names_pluralized"` - EnumFieldNamesPrefix CustomizableSeverityOption `yaml:"enum_field_names_prefix"` - EnumFieldNamesUpperSnakeCase CustomizableSeverityOption `yaml:"enum_field_names_upper_snake_case"` - EnumNamesUpperCamelCase CustomizableSeverityOption `yaml:"enum_names_upper_camel_case"` - FieldNamesLowerSnakeCase CustomizableSeverityOption `yaml:"field_names_lower_snake_case"` - FileHasComment CustomizableSeverityOption `yaml:"file_has_comment"` - MessageNamesUpperCamelCase CustomizableSeverityOption `yaml:"message_names_upper_camel_case"` - Order CustomizableSeverityOption `yaml:"order"` - PackageNameLowerCase CustomizableSeverityOption `yaml:"package_name_lower_case"` - Proto3FieldsAvoidRequired CustomizableSeverityOption `yaml:"proto3_fields_avoid_required"` - Proto3GroupsAvoid CustomizableSeverityOption `yaml:"proto3_groups_avoid"` - RPCNamesUpperCamelCase CustomizableSeverityOption `yaml:"rpc_names_upper_camel_case"` - ServiceNamesUpperCamelCase CustomizableSeverityOption `yaml:"service_names_upper_caml_case"` + FileNamesLowerSnakeCase FileNamesLowerSnakeCaseOption `yaml:"file_names_lower_snake_case" json:"file_names_lower_snake_case" toml:"file_names_lower_snake_case"` + QuoteConsistentOption QuoteConsistentOption `yaml:"quote_consistent" json:"quote_consistent" toml:"quote_consistent"` + ImportsSorted ImportsSortedOption `yaml:"imports_sorted" json:"imports_sorted" toml:"imports_sorted"` + MaxLineLength MaxLineLengthOption `yaml:"max_line_length" json:"max_line_length" toml:"max_line_length"` + Indent IndentOption `yaml:"indent" json:"indent" toml:"indent"` + EnumFieldNamesZeroValueEndWith EnumFieldNamesZeroValueEndWithOption `yaml:"enum_field_names_zero_value_end_with" json:"enum_field_names_zero_value_end_with" toml:"enum_field_names_zero_value_end_with"` + ServiceNamesEndWith ServiceNamesEndWithOption `yaml:"service_names_end_with" json:"service_names_end_with" toml:"service_names_end_with"` + FieldNamesExcludePrepositions FieldNamesExcludePrepositionsOption `yaml:"field_names_exclude_prepositions" json:"field_names_exclude_prepositions" toml:"field_names_exclude_prepositions"` + MessageNamesExcludePrepositions MessageNamesExcludePrepositionsOption `yaml:"message_names_exclude_prepositions" json:"message_names_exclude_prepositions" toml:"message_names_exclude_prepositions"` + RPCNamesCaseOption RPCNamesCaseOption `yaml:"rpc_names_case" json:"rpc_names_case" toml:"rpc_names_case"` + MessagesHaveComment MessagesHaveCommentOption `yaml:"messages_have_comment" json:"messages_have_comment" toml:"messages_have_comment"` + ServicesHaveComment ServicesHaveCommentOption `yaml:"services_have_comment" json:"services_have_comment" toml:"services_have_comment"` + RPCsHaveComment RPCsHaveCommentOption `yaml:"rpcs_have_comment" json:"rpcs_have_comment" toml:"rpcs_have_comment"` + FieldsHaveComment FieldsHaveCommentOption `yaml:"fields_have_comment" json:"fields_have_comment" toml:"fields_have_comment"` + EnumsHaveComment EnumsHaveCommentOption `yaml:"enums_have_comment" json:"enums_have_comment" toml:"enums_have_comment"` + EnumFieldsHaveComment EnumFieldsHaveCommentOption `yaml:"enum_fields_have_comment" json:"enum_fields_have_comment" toml:"enum_fields_have_comment"` + SyntaxConsistent SyntaxConsistentOption `yaml:"syntax_consistent" json:"syntax_consistent" toml:"syntax_consistent"` + RepeatedFieldNamesPluralized RepeatedFieldNamesPluralizedOption `yaml:"repeated_field_names_pluralized" json:"repeated_field_names_pluralized" toml:"repeated_field_names_pluralized"` + EnumFieldNamesPrefix CustomizableSeverityOption `yaml:"enum_field_names_prefix" json:"enum_field_names_prefix" toml:"enum_field_names_prefix"` + EnumFieldNamesUpperSnakeCase CustomizableSeverityOption `yaml:"enum_field_names_upper_snake_case" json:"enum_field_names_upper_snake_case" toml:"enum_field_names_upper_snake_case"` + EnumNamesUpperCamelCase CustomizableSeverityOption `yaml:"enum_names_upper_camel_case" json:"enum_names_upper_camel_case" toml:"enum_names_upper_camel_case"` + FieldNamesLowerSnakeCase CustomizableSeverityOption `yaml:"field_names_lower_snake_case" json:"field_names_lower_snake_case" toml:"field_names_lower_snake_case"` + FileHasComment CustomizableSeverityOption `yaml:"file_has_comment" json:"file_has_comment" toml:"file_has_comment"` + MessageNamesUpperCamelCase CustomizableSeverityOption `yaml:"message_names_upper_camel_case" json:"message_names_upper_camel_case" toml:"message_names_upper_camel_case"` + Order CustomizableSeverityOption `yaml:"order" json:"order" toml:"order"` + PackageNameLowerCase CustomizableSeverityOption `yaml:"package_name_lower_case" json:"package_name_lower_case" toml:"package_name_lower_case"` + Proto3FieldsAvoidRequired CustomizableSeverityOption `yaml:"proto3_fields_avoid_required" json:"proto3_fields_avoid_required" toml:"proto3_fields_avoid_required"` + Proto3GroupsAvoid CustomizableSeverityOption `yaml:"proto3_groups_avoid" json:"proto3_groups_avoid" toml:"proto3_groups_avoid"` + RPCNamesUpperCamelCase CustomizableSeverityOption `yaml:"rpc_names_upper_camel_case" json:"rpc_names_upper_camel_case" toml:"rpc_names_upper_camel_case"` + ServiceNamesUpperCamelCase CustomizableSeverityOption `yaml:"service_names_upper_caml_case" json:"service_names_upper_caml_case" toml:"service_names_upper_caml_case"` } diff --git a/internal/linter/config/serviceNamesEndWithOption.go b/internal/linter/config/serviceNamesEndWithOption.go index 4b4d8e5c..a1ecb7cb 100644 --- a/internal/linter/config/serviceNamesEndWithOption.go +++ b/internal/linter/config/serviceNamesEndWithOption.go @@ -3,5 +3,5 @@ package config // ServiceNamesEndWithOption represents the option for the SERVICE_NAMES_END_WITH rule. type ServiceNamesEndWithOption struct { CustomizableSeverityOption - Text string `yaml:"text"` + Text string `yaml:"text" json:"text" toml:"text"` } diff --git a/internal/linter/config/servicesHaveCommentOption.go b/internal/linter/config/servicesHaveCommentOption.go index 635c9a9e..b4a79241 100644 --- a/internal/linter/config/servicesHaveCommentOption.go +++ b/internal/linter/config/servicesHaveCommentOption.go @@ -3,5 +3,5 @@ package config // ServicesHaveCommentOption represents the option for the SERVICES_HAVE_COMMENT rule. type ServicesHaveCommentOption struct { CustomizableSeverityOption - ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style"` + ShouldFollowGolangStyle bool `yaml:"should_follow_golang_style" json:"should_follow_golang_style" toml:"should_follow_golang_style"` } diff --git a/internal/linter/config/syntaxConsistentOption.go b/internal/linter/config/syntaxConsistentOption.go index fa8b2c40..d818a0d6 100644 --- a/internal/linter/config/syntaxConsistentOption.go +++ b/internal/linter/config/syntaxConsistentOption.go @@ -3,5 +3,5 @@ package config // SyntaxConsistentOption represents the option for the SYNTAX_CONSISTENT rule. type SyntaxConsistentOption struct { CustomizableSeverityOption - Version string `yaml:"version"` + Version string `yaml:"version" json:"version" toml:"version"` } diff --git a/internal/linter/config/tomlExternalConfigProvider.go b/internal/linter/config/tomlExternalConfigProvider.go new file mode 100644 index 00000000..c1608143 --- /dev/null +++ b/internal/linter/config/tomlExternalConfigProvider.go @@ -0,0 +1,59 @@ +package config + +import ( + "github.com/BurntSushi/toml" +) + +const pyProjectTomlFileNameForPy = "pyproject.toml" +const pyProjectTomlFileNameForPyExtension = ".toml" + +type tomlConfigLoader struct { + filePath string +} + +func (t tomlConfigLoader) LoadExternalConfig() (*ExternalConfig, error) { + data, err := loadFileContent(t.filePath) + if err != nil { + return nil, err + } + + var config ExternalConfig + var tomlData tomlToolsEmbeddedConfig + // do not unmarshal strict. JS specific package.json will contain + // other values as well. + if tomlErr := toml.Unmarshal(data, &tomlData); tomlErr != nil { + return nil, tomlErr + } + + readConfig := tomlData.toExternalConfig() + if readConfig == nil { + return nil, nil + } + config = *readConfig + + config.SourcePath = t.filePath + + return &config, nil +} + +type tomlEmbeddedConfig struct { + Protolint *Lint `toml:"protolint"` +} + +type tomlToolsEmbeddedConfig struct { + Tools *tomlEmbeddedConfig `toml:"tools"` +} + +func (p tomlToolsEmbeddedConfig) toExternalConfig() *ExternalConfig { + if p.Tools == nil { + return nil + } + + if p.Tools.Protolint == nil { + return nil + } + + return &ExternalConfig{ + Lint: *p.Tools.Protolint, + } +}