From bcb3e23dafd52d32a33a1ed77dd3227200b8bfb4 Mon Sep 17 00:00:00 2001 From: Patrick_Audley Date: Fri, 14 Nov 2025 17:33:53 -0700 Subject: [PATCH 1/3] add shellcheck --- SELECTION.md | 3 +- .../cli/commands/lint/cli_models/overrides.py | 2 +- src/pyqa/config/models/sections/execution.py | 2 +- src/pyqa/parsers/__init__.py | 2 + src/pyqa/parsers/misc.py | 72 ++++++++++++ tests/test_cli_config.py | 2 +- tests/test_config.py | 2 +- tests/test_config_loader.py | 2 +- tests/test_parsers.py | 18 +++ tests/test_shellcheck_tool.py | 53 +++++++++ tests/test_tool_catalog_registry.py | 1 + tooling/catalog/cache.json | 49 +------- tooling/catalog/docs/shellcheck_cmd_help.txt | 21 ++++ tooling/catalog/docs/shellcheck_help.txt | 21 ++++ tooling/catalog/docs/sqlfluff_cmd_help.txt | 2 +- tooling/catalog/docs/tool_help_summary.json | 6 + .../catalog/languages/shell/shellcheck.json | 106 ++++++++++++++++++ 17 files changed, 314 insertions(+), 50 deletions(-) create mode 100644 tests/test_shellcheck_tool.py create mode 100644 tooling/catalog/docs/shellcheck_cmd_help.txt create mode 100644 tooling/catalog/docs/shellcheck_help.txt create mode 100644 tooling/catalog/languages/shell/shellcheck.json diff --git a/SELECTION.md b/SELECTION.md index f065d0b..2550ad0 100644 --- a/SELECTION.md +++ b/SELECTION.md @@ -112,11 +112,12 @@ Given a prepared lint state, tool selection proceeds as follows: ### Tool catalogue snapshot * **External** (catalog-sourced): black, isort, prettier, ruff-format, shfmt, + shellcheck, actionlint, bandit, cargo-clippy, cargo-fmt, checkmake, cpplint, dockerfilelint, dotenv-linter, eslint, gofmt, golangci-lint, gts, hadolint, luacheck, lualint, mdformat, perlcritic, perltidy, phplint, pylint, pyupgrade, remark-lint, ruff, selene, speccy, sqlfluff, stylelint, tombi, - tsc, yamllint, kube-linter, mypy, pyright (38 tools in total). + tsc, yamllint, kube-linter, mypy, pyright (39 tools in total). * **Internal** (phase‑8/10, repo-agnostic): docstrings, suppressions, types, closures, signatures, cache, value-types, license-header, copyright, python-hygiene, file-size. diff --git a/src/pyqa/cli/commands/lint/cli_models/overrides.py b/src/pyqa/cli/commands/lint/cli_models/overrides.py index 42b5d21..965abd0 100644 --- a/src/pyqa/cli/commands/lint/cli_models/overrides.py +++ b/src/pyqa/cli/commands/lint/cli_models/overrides.py @@ -23,7 +23,7 @@ def _override_formatting_dependency( line_length: Annotated[int, typer.Option(120, "--line-length", help=LINE_LENGTH_HELP)], - sql_dialect: Annotated[str, typer.Option("postgresql", "--sql-dialect", help=SQL_DIALECT_HELP)], + sql_dialect: Annotated[str, typer.Option("postgres", "--sql-dialect", help=SQL_DIALECT_HELP)], python_version: Annotated[str | None, typer.Option(None, "--python-version", help=PYTHON_VERSION_HELP)], ) -> OverrideFormattingParams: """Return formatting overrides shared across compatible tools. diff --git a/src/pyqa/config/models/sections/execution.py b/src/pyqa/config/models/sections/execution.py index 2336d7e..49a9413 100644 --- a/src/pyqa/config/models/sections/execution.py +++ b/src/pyqa/config/models/sections/execution.py @@ -45,7 +45,7 @@ class ExecutionConfig(BaseModel): bail: bool = False use_local_linters: bool = False line_length: int = 120 - sql_dialect: str = "postgresql" + sql_dialect: str = "postgres" python_version: str | None = None diff --git a/src/pyqa/parsers/__init__.py b/src/pyqa/parsers/__init__.py index d414259..8117468 100644 --- a/src/pyqa/parsers/__init__.py +++ b/src/pyqa/parsers/__init__.py @@ -21,6 +21,7 @@ parse_golangci_lint, parse_perlcritic, parse_phplint, + parse_shellcheck, parse_shfmt, parse_tombi, ) @@ -55,6 +56,7 @@ "parse_remark", "parse_ruff", "parse_selene", + "parse_shellcheck", "parse_shfmt", "parse_speccy", "parse_sqlfluff", diff --git a/src/pyqa/parsers/misc.py b/src/pyqa/parsers/misc.py index 84fd53b..ff9534d 100644 --- a/src/pyqa/parsers/misc.py +++ b/src/pyqa/parsers/misc.py @@ -37,6 +37,12 @@ "warning": Severity.WARNING, "info": Severity.NOTICE, } +SHELLCHECK_SEVERITY_MAP: Final[dict[str, Severity]] = { + "error": Severity.ERROR, + "warning": Severity.WARNING, + "info": Severity.NOTICE, + "style": Severity.NOTE, +} def parse_shfmt(stdout: Sequence[str], context: ToolContext) -> Sequence[RawDiagnostic]: @@ -283,6 +289,71 @@ def parse_cpplint(stdout: Sequence[str], context: ToolContext) -> Sequence[RawDi return results +def parse_shellcheck(payload: JsonValue, context: ToolContext) -> Sequence[RawDiagnostic]: + """Parse shellcheck JSON payload into canonical diagnostics. + + Args: + payload: Parsed JSON payload emitted by shellcheck. + context: Tool execution context supplied by the orchestrator. + + Returns: + Sequence[RawDiagnostic]: Diagnostics describing shellcheck findings. + """ + + del context + diagnostics: list[RawDiagnostic] = [] + for entry in _iter_shellcheck_entries(payload): + message = coerce_optional_str(entry.get("message")) + if not message: + continue + severity = map_severity( + entry.get("level"), + SHELLCHECK_SEVERITY_MAP, + Severity.WARNING, + ) + location = DiagnosticLocation( + file=coerce_optional_str(entry.get("file")), + line=coerce_optional_int(entry.get("line")), + column=coerce_optional_int(entry.get("column")), + ) + details = DiagnosticDetails( + severity=severity, + message=message.strip(), + tool="shellcheck", + code=_format_shellcheck_code(entry.get("code")), + ) + diagnostics.append(create_spec(location=location, details=details).build()) + return diagnostics + + +def _iter_shellcheck_entries(payload: JsonValue) -> tuple[Mapping[str, JsonValue], ...]: + """Return shellcheck diagnostic entries regardless of payload shape.""" + + if isinstance(payload, Mapping): + comments = payload.get("comments") + if comments is not None: + return tuple(mapping_sequence(comments)) + return tuple(mapping_sequence(payload)) + return tuple(mapping_sequence(payload)) + + +def _format_shellcheck_code(value: JsonValue | None) -> str | None: + """Return shellcheck rule identifiers prefixed with ``SC``.""" + + if value is None: + return None + if isinstance(value, int): + return f"SC{value}" + text = str(value).strip() + if not text: + return None + try: + numeric = int(text) + except ValueError: + return text + return f"SC{numeric}" + + _ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m") _TOMBI_HEADER_RE = re.compile( r"^(?PError|Warning|Info|Hint|Note):\s*(?P.+)$", @@ -520,6 +591,7 @@ def parse_cargo_clippy(payload: JsonValue, context: ToolContext) -> Sequence[Raw "parse_golangci_lint", "parse_perlcritic", "parse_phplint", + "parse_shellcheck", "parse_shfmt", "parse_tombi", ] diff --git a/tests/test_cli_config.py b/tests/test_cli_config.py index 0146ce6..bb0318c 100644 --- a/tests/test_cli_config.py +++ b/tests/test_cli_config.py @@ -38,7 +38,7 @@ def test_config_show_outputs_json(tmp_path: Path, monkeypatch) -> None: payload = json.loads(stdout) assert payload["execution"]["jobs"] == 3 assert payload["execution"]["line_length"] == 120 - assert payload["execution"]["sql_dialect"] == "postgresql" + assert payload["execution"]["sql_dialect"] == "postgres" assert payload["tool_settings"]["black"]["line-length"] == 88 diff --git a/tests/test_config.py b/tests/test_config.py index 36a9821..0b2686b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -66,7 +66,7 @@ def _build_options( use_local_linters: bool = False, strict_config: bool = False, line_length: int = 120, - sql_dialect: str = "postgresql", + sql_dialect: str = "postgres", python_version: str | None = None, max_complexity: int | None = None, max_arguments: int | None = None, diff --git a/tests/test_config_loader.py b/tests/test_config_loader.py index fd5e930..4f7bdbd 100644 --- a/tests/test_config_loader.py +++ b/tests/test_config_loader.py @@ -26,7 +26,7 @@ def test_load_config_defaults(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) - assert cfg.file_discovery.roots == [project_root.resolve()] assert cfg.execution.cache_dir == (project_root / ".lint-cache").resolve() assert cfg.execution.line_length == 120 - assert cfg.execution.sql_dialect == "postgresql" + assert cfg.execution.sql_dialect == "postgres" assert cfg.output.pr_summary_out is None diff --git a/tests/test_parsers.py b/tests/test_parsers.py index 7403e94..170bb32 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -32,6 +32,7 @@ parse_remark, parse_ruff, parse_selene, + parse_shellcheck, parse_shfmt, parse_speccy, parse_sqlfluff, @@ -113,6 +114,23 @@ def test_parse_mypy_single_object() -> None: assert diag.severity.value == "error" +def test_parse_shellcheck() -> None: + parser = JsonParser(parse_shellcheck) + stdout = """ + [ + {"file": "scripts/run.sh", "line": 5, "column": 2, "level": "info", "code": 2086, "message": "Double quote to prevent globbing."} + ] + """ + diags = parser.parse(stdout, "", context=_ctx()) + assert len(diags) == 1 + diag = diags[0] + assert diag.file == "scripts/run.sh" + assert diag.line == 5 + assert diag.column == 2 + assert diag.code == "SC2086" + assert diag.severity.value == "notice" + + def test_parse_actionlint() -> None: parser = JsonParser(parse_actionlint) stdout = """ diff --git a/tests/test_shellcheck_tool.py b/tests/test_shellcheck_tool.py new file mode 100644 index 0000000..77c30e1 --- /dev/null +++ b/tests/test_shellcheck_tool.py @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 Blackcat Informatics® Inc. +"""Tests for shellcheck catalog definition.""" + +from __future__ import annotations + +from pathlib import Path + +from pyqa.catalog import ToolCatalogLoader +from pyqa.catalog.strategies import command_option_map +from pyqa.config import Config +from pyqa.tools.base import ToolAction, ToolContext + +_PYQA_ROOT = Path(__file__).resolve().parents[1] +_CATALOG_ROOT = _PYQA_ROOT / "tooling" / "catalog" + + +def _shellcheck_config() -> dict[str, object]: + loader = ToolCatalogLoader(catalog_root=_CATALOG_ROOT) + snapshot = loader.load_snapshot() + for definition in snapshot.tools: + if definition.name != "shellcheck": + continue + for action in definition.actions: + if action.name == "lint": + return dict(action.command.reference.config) + raise AssertionError("shellcheck lint action missing from catalog") + + +def test_shellcheck_command(tmp_path: Path) -> None: + ctx = ToolContext( + cfg=Config(), + root=tmp_path, + files=[tmp_path / "script.sh"], + settings={ + "severity": "error", + "exclude": ["SC1000", "SC2000"], + "external-sources": True, + "shell": "bash", + }, + ) + builder = command_option_map(_shellcheck_config()) + action = ToolAction(name="lint", command=builder) + + command = action.build_command(ctx) + assert command[:3] == ["shellcheck", "--color=never", "--format=json"] + assert "--severity=error" in command + assert "--exclude" in command + exclude_idx = command.index("--exclude") + assert "SC1000" in command[exclude_idx + 1] + assert "--external-sources" in command + assert "--shell=bash" in command + assert str(ctx.files[0]) in command diff --git a/tests/test_tool_catalog_registry.py b/tests/test_tool_catalog_registry.py index 44b38df..27b2abe 100644 --- a/tests/test_tool_catalog_registry.py +++ b/tests/test_tool_catalog_registry.py @@ -117,6 +117,7 @@ def test_initialize_registry_real_catalog(schema_root: Path, tmp_path: Path) -> "ruff", "ruff-format", "selene", + "shellcheck", "shfmt", "speccy", "sqlfluff", diff --git a/tooling/catalog/cache.json b/tooling/catalog/cache.json index 29de61e..32f69a4 100644 --- a/tooling/catalog/cache.json +++ b/tooling/catalog/cache.json @@ -1,19 +1,10 @@ { - "checksum": "d713394391d096328f843b51a4c38407ec7bd895c61c7f61880290b6c1d83bc2", + "checksum": "7acec60806965d37533a3cbf568ce5474103ffd54d817059abab2530f0b4c967", "files": [ "_shared/_go_runtime.json", "_shared/_npm_runtime.json", "_shared/_python_defaults.json", "docs/SHARED_KNOBS.md", - "docs/_scratch/Dockerfile", - "docs/_scratch/sample.js", - "docs/_scratch/sample.lua", - "docs/_scratch/sample.php", - "docs/_scratch/sample.py", - "docs/_scratch/sample.sql", - "docs/_scratch/sample.ts", - "docs/_scratch/sample.txt", - "docs/_scratch/sample.yaml", "docs/actionlint_cmd_help.txt", "docs/actionlint_help.txt", "docs/bandit_cmd_help.txt", @@ -78,6 +69,8 @@ "docs/ruff_help.txt", "docs/selene_cmd_help.txt", "docs/selene_help.txt", + "docs/shellcheck_cmd_help.txt", + "docs/shellcheck_help.txt", "docs/shfmt_cmd_help.txt", "docs/shfmt_help.txt", "docs/speccy_cmd_help.txt", @@ -127,47 +120,17 @@ "languages/python/ruff.json", "languages/rust/cargo-clippy.json", "languages/rust/cargo-fmt.json", + "languages/shell/shellcheck.json", "languages/shell/shfmt.json", "languages/sql/sqlfluff.json", "languages/toml/tombi.json", "languages/yaml/yamllint.json", "strategies/command_download_binary.json", - "strategies/command_black.json", - "strategies/command_cargo_clippy.json", - "strategies/command_cargo_fmt.json", - "strategies/command_checkmake.json", - "strategies/command_cpplint.json", - "strategies/command_dockerfilelint.json", - "strategies/command_dotenv_linter.json", - "strategies/command_eslint.json", - "strategies/command_gofmt.json", - "strategies/command_golangci_lint.json", - "strategies/command_gts.json", + "strategies/command_option_map.json", "strategies/command_project_scanner.json", - "strategies/command_isort.json", - "strategies/command_kube_linter.json", - "strategies/command_luacheck.json", - "strategies/command_lualint.json", - "strategies/command_mdformat.json", - "strategies/command_mypy.json", - "strategies/command_perlcritic.json", - "strategies/command_perltidy.json", - "strategies/command_phplint.json", - "strategies/command_prettier.json", - "strategies/command_pylint.json", - "strategies/command_pyright.json", - "strategies/command_remark_lint.json", - "strategies/command_ruff.json", - "strategies/command_ruff_format.json", - "strategies/command_selene.json", - "strategies/command_speccy.json", - "strategies/command_sqlfluff.json", - "strategies/command_stylelint.json", - "strategies/command_tsc.json", - "strategies/command_yamllint.json", "strategies/installer_download_artifact.json", "strategies/json_parser.json", - "strategies/pyupgrade_command.json", + "strategies/parser_json_diagnostics.json", "strategies/text_parser.json" ] } diff --git a/tooling/catalog/docs/shellcheck_cmd_help.txt b/tooling/catalog/docs/shellcheck_cmd_help.txt new file mode 100644 index 0000000..0e09225 --- /dev/null +++ b/tooling/catalog/docs/shellcheck_cmd_help.txt @@ -0,0 +1,21 @@ +shellcheck --color=never --format=json --help + +Usage: shellcheck [OPTIONS...] FILES... + -a --check-sourced Include warnings from sourced files + -C[WHEN] --color[=WHEN] Use color (auto, always, never) + -i CODE1,CODE2.. --include=CODE1,CODE2.. Consider only given types of warnings + -e CODE1,CODE2.. --exclude=CODE1,CODE2.. Exclude types of warnings + --extended-analysis=bool Perform dataflow analysis (default true) + -f FORMAT --format=FORMAT Output format (checkstyle, diff, gcc, json, json1, quiet, tty) + --list-optional List checks disabled by default + --norc Don't look for .shellcheckrc files + --rcfile=RCFILE Prefer the specified configuration file over searching for one + -o check1,check2.. --enable=check1,check2.. List of optional checks to enable (or 'all') + -P SOURCEPATHS --source-path=SOURCEPATHS Specify path when looking for sourced files ("SCRIPTDIR" for script's dir) + -s SHELLNAME --shell=SHELLNAME Specify dialect (sh, bash, dash, ksh, busybox) + -S SEVERITY --severity=SEVERITY Minimum severity of errors to consider (error, warning, info, style) + -V --version Print version information + -W NUM --wiki-link-count=NUM The number of wiki links to show, when applicable + -x --external-sources Allow 'source' outside of FILES + --help Show this usage summary and exit + diff --git a/tooling/catalog/docs/shellcheck_help.txt b/tooling/catalog/docs/shellcheck_help.txt new file mode 100644 index 0000000..7293b64 --- /dev/null +++ b/tooling/catalog/docs/shellcheck_help.txt @@ -0,0 +1,21 @@ +shellcheck --help + +Usage: shellcheck [OPTIONS...] FILES... + -a --check-sourced Include warnings from sourced files + -C[WHEN] --color[=WHEN] Use color (auto, always, never) + -i CODE1,CODE2.. --include=CODE1,CODE2.. Consider only given types of warnings + -e CODE1,CODE2.. --exclude=CODE1,CODE2.. Exclude types of warnings + --extended-analysis=bool Perform dataflow analysis (default true) + -f FORMAT --format=FORMAT Output format (checkstyle, diff, gcc, json, json1, quiet, tty) + --list-optional List checks disabled by default + --norc Don't look for .shellcheckrc files + --rcfile=RCFILE Prefer the specified configuration file over searching for one + -o check1,check2.. --enable=check1,check2.. List of optional checks to enable (or 'all') + -P SOURCEPATHS --source-path=SOURCEPATHS Specify path when looking for sourced files ("SCRIPTDIR" for script's dir) + -s SHELLNAME --shell=SHELLNAME Specify dialect (sh, bash, dash, ksh, busybox) + -S SEVERITY --severity=SEVERITY Minimum severity of errors to consider (error, warning, info, style) + -V --version Print version information + -W NUM --wiki-link-count=NUM The number of wiki links to show, when applicable + -x --external-sources Allow 'source' outside of FILES + --help Show this usage summary and exit + diff --git a/tooling/catalog/docs/sqlfluff_cmd_help.txt b/tooling/catalog/docs/sqlfluff_cmd_help.txt index a8f3516..839828d 100644 --- a/tooling/catalog/docs/sqlfluff_cmd_help.txt +++ b/tooling/catalog/docs/sqlfluff_cmd_help.txt @@ -1,4 +1,4 @@ -sqlfluff lint --format json --dialect postgresql --help +sqlfluff lint --format json --dialect postgres --help Usage: sqlfluff lint [OPTIONS] [PATHS]... diff --git a/tooling/catalog/docs/tool_help_summary.json b/tooling/catalog/docs/tool_help_summary.json index fa087b7..f27ed27 100644 --- a/tooling/catalog/docs/tool_help_summary.json +++ b/tooling/catalog/docs/tool_help_summary.json @@ -137,6 +137,12 @@ "arguments": true, "strict": false }, + "shellcheck": { + "line_length": false, + "complexity": false, + "arguments": true, + "strict": false + }, "phplint": { "line_length": false, "complexity": false, diff --git a/tooling/catalog/languages/shell/shellcheck.json b/tooling/catalog/languages/shell/shellcheck.json new file mode 100644 index 0000000..a63192d --- /dev/null +++ b/tooling/catalog/languages/shell/shellcheck.json @@ -0,0 +1,106 @@ +{ + "schemaVersion": "1.0.0", + "name": "shellcheck", + "description": "Static analysis for POSIX and bash shell scripts.", + "languages": ["shell"], + "phase": "lint", + "fileExtensions": [".sh", ".bash", ".zsh"], + "runtime": { + "type": "binary", + "versionCommand": ["shellcheck", "--version"] + }, + "options": [ + { + "name": "severity", + "type": "str", + "description": "Minimum severity to report (error, warning, info, style)." + }, + { + "name": "shell", + "type": "str", + "description": "Explicit shell dialect (bash, sh, dash, ksh, zsh, mksh)." + }, + { + "name": "exclude", + "type": "list[str]", + "description": "Rule identifiers (SC####) to ignore." + }, + { + "name": "source-path", + "type": "path", + "aliases": ["source_path"], + "description": "Additional lookup path for sourced files." + }, + { + "name": "external-sources", + "type": "bool", + "aliases": ["external_sources"], + "description": "Allow following source statements outside the current directory." + }, + { + "name": "args", + "type": "list[str]", + "description": "Additional arguments appended to shellcheck." + } + ], + "actions": [ + { + "name": "lint", + "command": { + "strategy": "command_option_map", + "config": { + "base": ["shellcheck", "--color=never", "--format=json"], + "appendFiles": false, + "options": [ + { + "setting": "severity", + "type": "value", + "flag": "--severity=" + }, + { + "setting": "shell", + "type": "value", + "flag": "--shell=" + }, + { + "setting": "exclude", + "type": "args", + "flag": "--exclude", + "joinWith": "," + }, + { + "setting": ["source-path", "source_path"], + "type": "path", + "flag": "--source-path" + }, + { + "setting": ["external-sources", "external_sources"], + "type": "flag", + "flag": "--external-sources" + }, + { + "setting": "args", + "type": "args" + } + ] + } + }, + "parser": { + "strategy": "parser_json", + "config": { + "transform": "pyqa.parsers.misc.parse_shellcheck" + } + } + } + ], + "documentation": { + "help": { + "path": "docs/shellcheck_help.txt", + "format": "text" + }, + "commandHelp": { + "path": "docs/shellcheck_cmd_help.txt", + "format": "text" + } + } +} From 5cbbb49e856aa941adc5bd5f0af6e4b875748eaf Mon Sep 17 00:00:00 2001 From: Patrick_Audley Date: Fri, 14 Nov 2025 17:38:11 -0700 Subject: [PATCH 2/3] tweak license so it shows correctly --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6800869..427e1e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,10 @@ name = "pyqa_lint" version = "0.3.0" description = "Polyglot lint orchestration toolkit" readme = "README.md" -license = { file = "LICENSE" } +license = { text = "MIT License" } +classifiers = [ + "License :: OSI Approved :: MIT License", +] requires-python = ">=3.13" dependencies = [ "autopep8>=2.3.2", From 12e8bae88f0ae68f7544e17a184e366cd8b0e4c5 Mon Sep 17 00:00:00 2001 From: Patrick_Audley Date: Fri, 14 Nov 2025 18:21:16 -0700 Subject: [PATCH 3/3] bump tool schema --- ref_docs/tool-schema.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ref_docs/tool-schema.json b/ref_docs/tool-schema.json index f11a09b..e81b95e 100644 --- a/ref_docs/tool-schema.json +++ b/ref_docs/tool-schema.json @@ -1057,6 +1057,32 @@ "type": "bool" } }, + "shellcheck": { + "args": { + "description": "Additional arguments appended to shellcheck.", + "type": "list[str]" + }, + "exclude": { + "description": "Rule identifiers (SC####) to ignore.", + "type": "list[str]" + }, + "external-sources": { + "description": "Allow following source statements outside the current directory.", + "type": "bool" + }, + "severity": { + "description": "Minimum severity to report (error, warning, info, style).", + "type": "str" + }, + "shell": { + "description": "Explicit shell dialect (bash, sh, dash, ksh, zsh, mksh).", + "type": "str" + }, + "source-path": { + "description": "Additional lookup path for sourced files.", + "type": "path" + } + }, "speccy": { "args": { "description": "Additional arguments appended to speccy.",