This is a command-line interface to the Z PL/SQL Analyzer. It is a code analyzer for Oracle PL/SQL and Oracle Forms projects.
Official releases are available for download on the "Releases" page.
- Java 21 or newer
Currently, the zpa-cli supports these options:
--sources: [required] Path to the folder containing project files. Defines the complete project source/context set.--files: One or more files to analyze, separated by space (e.g.--files a.pks b.pkbor repeated--files a.pks --files b.pkb), or-to read from standard input. Relative paths are resolved relative to--sources. Absolute paths are accepted only when they resolve inside--sources. For normal analysis, every target must be a member of the discovered sources under--sources. When omitted, all supported files discovered under--sourcesare analyzed.--syntax-only: Validates PL/SQL syntax only. Bypasses normal coding rules, custom plugin loading, Forms metadata, and project semantic preparation. When used with explicit--files, only the specified targets are resolved and parsed without recursively discovering the rest of the project.--stdin-filename: Filename for source identity when reading from stdin (--files -), relative to--sourcesor absolute. Must reside inside--sourcesand have a supported extension. In normal analysis it is required and names the project file the input stands for (see Analyzing stdin with the project context); with--syntax-onlyit defaults tostdin.sql.--context-overlay <path> <file>(fork, repeatable): Uses the content of<file>for the project file<path>in the project context, without analyzing it (e.g. other unsaved editor buffers). See Context overlays.--fail-on: Failure threshold for the exit code (none,any,syntax,blocker,critical,major,minor,info). In normal analysis mode, the default isnone. When--syntax-onlyis requested without--fail-on, the default threshold issyntax. An explicit--fail-on noneoverrides this default in syntax-only mode.--forms-metadata: Path to the Oracle Forms metadata file.--extensions: File extensions to analyze, separated by comma. The default value issql,pkg,pks,pkb,fun,pcd,tgg,prc,tpb,trg,typ,tab,tps.--output-format: Format of the output. Supported formats:console,json,sq-generic-issue-import,sarif. The default value isconsole.--output-file: Path to the output file. When specified withjsonorsarif, writes the report to the file without writing to stdout.--config: Path to the configuration file. The file format must comply with the provided JSON schema. You can refer to the example zpa-config-example.json for guidance. If the configuration file is not provided, only the rules marked as "activated by default" will be executed.
--sourcesdefines the complete project context. All discovered project files are used for project declaration index preparation and cross-file semantic resolution.--filesoptionally restricts the files that are actually scanned for diagnostics and reported. Filesystem targets must be a subset of discovered project sources (filesystemTargets ⊆ discoveredProjectSources).- Standard input (
--files -) is analyzed as an overlay of one project file, see below. With--syntax-onlyit is only parsed. --context-overlaychanges the content of project files in the project context only, see below.
--files - --stdin-filename <path> without --syntax-only analyzes the content of standard input (e.g. an unsaved
editor buffer) as if the file <path> on disk had that content:
- The project declaration index is built from all files under
--sources, with the stdin content in place of the disk content of<path>. If<path>does not exist yet (a new, unsaved file), it is added to the project. - Only
<path>is analyzed, from the stdin content; other project files are used as context but not reported. - Issues are reported under
<path>relative to--sources, exactly as for--files <path>(all output formats, NOSONAR filter, quick fixes,--fail-on). If the file exists on disk, the spelling found on disk is used. - stdin is read as UTF-8, like the source files; a byte order mark is kept exactly as when reading a file.
--stdin-filenameis required, must be inside--sourcesand must have one of the--extensions.-cannot be combined with other--filesentries (analyze other files in a separate run). Violations exit with code 2 before stdin is read.- Nothing is written to
--sources.
In daemon mode the content is passed in the request's "stdin" field instead.
--context-overlay <path> <file> replaces the content of the project file <path> with the content of <file> in
the project context, e.g. for editor buffers with unsaved changes besides the one analyzed from stdin. The option takes
two values and can be repeated, once per file:
<path>is the project file, relative to--sourcesor absolute, and must be inside--sourceswith one of the--extensions. If it does not exist on disk (a new, unsaved file), it is added to the project context.<file>holds the content, typically a temporary file written by the client (relative paths resolve against the working directory). It is read as UTF-8 like the source files; a leading byte order mark is dropped.- The overlays are project context only: they are used for the project declaration index but never analyzed or
reported. Only the targets are analyzed:
--files -(stdin, see above) or--files <path>...on disk. - Two separate values instead of a
<path>=<file>syntax, so no separator can clash with characters in paths (Windows drive letters,=in file names). - Exit code 2 (before stdin is read) when
--context-overlayis combined with--syntax-onlyor used without--files, when<path>is outside--sourcesor has an unsupported extension, when<path>is given twice or is also a target (the--stdin-filenamefile or a--filesentry), when<file>does not exist or is not a file, or when a value is missing or empty. - Nothing is written to
--sources.
console: writes human-readable analysis results to standard output, grouped by file and sorted deterministically, including position, severity, rule key, and message.json: outputs a deterministic, schema-versioned machine-readable JSON document (schemaVersion: 1). Safe to pipe: stdout contains only JSON (when--output-fileis not used), while logs and progress messages go to stderr.validation:{ "passed": boolean, "threshold": string }. Authoritative validation outcome matching exit code 0 (passed: true) vs 1 (passed: false) against the configured--fail-onthreshold. Diagnostics may exist whenvalidation.passedis true (e.g. with--fail-on none).diagnostics: list of findings sorted stably by normalized file, range, rule, and message:kind:"SYNTAX"(parse/syntax diagnostics fromParsingErrorCheck) or"RULE"(standard coding rules).file: normalized file path relative to--sources.range:{ "startLine": int?, "startColumn": int?, "endLine": int?, "endColumn": int? }ornullfor file-level issues. Coordinates:startLineis 1-based,startColumnis 0-based,endLineis 1-based,endColumnis 0-based exclusive. Unavailable coordinates are represented asnull.rule: rule identifier (e.g.zpa:ParsingError,zpa:PackageBodyParameterNocopy).severity:"BLOCKER","CRITICAL","MAJOR","MINOR","INFO".message: localized diagnostic message.quickFixes(optional, fork): automatic corrections for the diagnostic, see Quick fixes. The field is omitted when the rule offers none.
sq-generic-issue-import: generates a JSON file using SonarQube's "Generic Issue Data" format that can be used in SonarCloud or in a SonarQube server. Issues with a quick fix additionally carry aquickFixesfield (fork, see Quick fixes); SonarQube ignores it.sarif(fork): generates a SARIF 2.1.0 log, consumable by tools such as GitHub code scanning'supload-sarifaction. Safe to pipe likejson(stdout contains only the SARIF document when--output-fileis not used). Onerun, with atool.driver.rulesentry per distinct rule found and oneresultper issue;regioncolumns are 1-based (SARIF's convention), unlike the 0-based columns of the other formats. Quick fixes are not included (SARIF has no standard field for them).
Some rules (e.g. InequalityUsage, ComparisonWithNull) offer automatic corrections. The json and
sq-generic-issue-import formats export them per issue as an optional quickFixes array, which is omitted when the
issue has none, so reports without quick fixes are unchanged. The console format appends [quick fix available] to
such issues.
"quickFixes": [
{
"message": "Change to \"IS NULL\"",
"edits": [
{ "startLine": 7, "startColumn": 5, "endLine": 7, "endColumn": 12, "text": "" },
{ "startLine": 7, "startColumn": 13, "endLine": 7, "endColumn": 13, "text": " IS NULL" }
]
}
]- An issue may offer several alternative quick fixes; each one has a
messageand a non-empty list ofedits. - Each edit replaces a range of the file of the issue with
text. The coordinates are the same as those of the issue location in the respective format (primaryLocation.textRange/range): lines are 1-based, columns are 0-based UTF-16 code units, andendColumnis exclusive. Unlike the issue location, all four coordinates are always present. An emptytextdeletes the range; an empty range insertstext. - All edits of a quick fix refer to the original file and must be applied together (e.g. from the last to the first).
They do not overlap. Quick fixes of different issues may overlap (for example
x <> NULLgets one fix fromInequalityUsageand one fromComparisonWithNull), so after applying one, the file should be analyzed again. - The
jsonschemaVersionstays1: the field is an additive, optional extension.
--fix applies the quick fixes to the analyzed files, e.g. in a CI job or a pre-commit hook:
zpa-cli --sources . --fix # fix all project files
zpa-cli --sources . --files src/packages/customer.pkb --fix # fix one file
zpa-cli --sources . --fix-dry-run > fixes.diff # show the changes, write nothing- The analysis runs as usual (
--sources,--files,--config,--extensions,--context-overlay, NOSONAR, ...). Then, per file, the first (preferred) quick fix of every issue is chosen in document order; a quick fix that overlaps one chosen before is skipped. Quick fixes starting at the same position are chosen by the severity of their issue (most severe first): forx <> NULL,ComparisonWithNull'sIS NOT NULLwins overInequalityUsage's!=. This is the same choice as "fix all" in zpa-for-vscode. - The fixed files are analyzed again, which finds skipped quick fixes and issues revealed by a fix. This repeats up
to
--fix-max-roundsrounds (default3), analyzing only the files changed in the round before, and stops early when nothing changes. A round whose fixes make a file unparsable is not applied to that file. - The report (
--output-format,--output-file,--fail-on) shows the issues that remain after the fixes, so a CI job can fail on what is left. A summary goes to stderr:Applied 4 quick fixes in 2 files (2 rounds) src/a.sql: 3 quick fixes src/b.sql: 1 quick fix - Files are only written at the end and only if a quick fix changed them, each atomically (a temporary file in the
same directory replaces the file). The encoding stays UTF-8, and the line separators and a leading byte order mark
are kept. Files that are not valid UTF-8 are analyzed but not fixed; a file that changed on disk during the
analysis is not written. If a file cannot be written, the exit code is
3. --fix-dry-rundoes the same without writing: it prints a unified diff of every file that would change to stdout (--- a/<path>/+++ b/<path>, relative to--sources, usable withgit apply) and reports the issues that would remain. With--output-format jsonit needs--output-file, since stdout carries the diff.- Rejected with exit code
2:--fix/--fix-dry-runwith--syntax-only(no rule offers quick fixes) or with standard input (--files -; fix the file on disk instead), and--fix-max-roundsbelow 1 or without--fix. - In daemon mode
--fixworks the same way; the files are written before the response is sent.
0: analysis completed without an enabled validation failure (threshold not exceeded)1: requested validation condition failed (findings met or exceeded the--fail-onthreshold)2: invalid invocation, command-line arguments, or target file error (e.g. nonexistent target file, target outside--sources, stdin used without--stdin-filenamein normal analysis)3: internal execution failure (with--fix, also: a fixed file could not be written)
Full project analysis:
zpa-cli --sources .Focused human validation:
zpa-cli --sources . --files src/packages/customer.pkbFocused machine validation:
zpa-cli --sources . --files src/packages/customer.pkb --output-format jsonSyntax validation:
zpa-cli --sources . --files src/packages/customer.pkb --syntax-onlyStdin validation:
cat generated.sql | zpa-cli --sources . --files - --stdin-filename src/packages/customer.pkb --syntax-onlyAnalysis of an unsaved buffer with the project context (fork):
cat buffer.pkb | zpa-cli --sources . --files - --stdin-filename src/packages/customer.pkbThe same, with the unsaved specification of the package as context (fork):
cat buffer.pkb | zpa-cli --sources . --files - --stdin-filename src/packages/customer.pkb \
--context-overlay src/packages/customer.pks /tmp/customer-spec-buffer.pksRunning an analysis:
./zpa-cli/bin/zpa-cli --sources . --output-file zpa-issues.json --output-format sq-generic-issue-import
Then you can send the results to a SonarCloud or SonarQube server setting the sonar.externalIssuesReportPaths property:
sonar-scanner
-Dsonar.organization=$SONARCLOUD_ORGANIZATION \
-Dsonar.projectKey=myproject \
-Dsonar.sources=. \
-Dsonar.host.url=https://sonarcloud.io \
-Dsonar.externalIssuesReportPaths=zpa-issues.json
Check the demo project on SonarCloud!
zpa-cli --daemon keeps one JVM running for many analyses (e.g. for an editor integration), avoiding the JVM
startup on every run. --daemon must be the only argument. The protocol is line-based JSON (UTF-8, one object per
\n-terminated line) on stdin/stdout:
- On start the daemon writes
{"type":"ready","protocol":1,"version":"<zpa-cli version>"}. - Each request line is
{"id": <string|number>, "args": ["--sources", "...", ...]}, whereargsare exactly the arguments of a normal invocation. Optional"stdin": "<text>"is the content read by--files -; without it, standard input is empty for the analysis. With--files - --stdin-filename <path>(and no--syntax-only) this analyzes an unsaved buffer with the project context, see Analyzing stdin with the project context. Other unsaved buffers can be passed as context overlays (--context-overlay <path> <file>inargs, with the content in temporary files written by the client). - Requests run sequentially. Each gets one response line
{"id": ..., "exitCode": <int>, "stdout": "...", "stderr": "..."}with the usual exit codes and everything the run wrote to stdout/stderr (including log output). Nothing else is written to stdout. - A malformed request line gets a response with
exitCode2 and the error instderr(idisnullif it could not be read); the daemon keeps running. {"type":"shutdown"}or the end of stdin stops the daemon with exit code 0.
Relative paths (--sources, --output-file, --config, ...) resolve against the directory the daemon was started
in, not per request, so clients should pass absolute paths.
The plugins in the plugins folder are loaded by the first analysis request and reused by the following ones; the
rules, their configuration and the check instances are still set up per request. Before every analysis the daemon
compares the plugin JARs (file name, size and modification time) with the loaded ones: when a JAR was added, replaced
or removed, the plugins are unloaded and loaded again, so changes are picked up without restarting the daemon. The
loaded plugins are released when the daemon stops.
The zpa-cli jar (lib/zpa-cli-<version>.jar) contains META-INF/zpa-cli-capabilities.properties, so clients can
detect the fork features of an installation offline, without starting it:
daemon=1
stdin-project=1
quick-fixes=1
context-overlays=1
fix=1
sarif=1daemon: daemon mode.stdin-project: analyzing stdin with the project context.quick-fixes: quick fixes in thejsonandsq-generic-issue-importformats.context-overlays: context overlays (--context-overlay <path> <file>).fix: fix mode (--fix,--fix-dry-run,--fix-max-rounds).sarif:--output-format sarif.
This is a stable contract: there is one key per fork feature present in the build, and its value is the revision of the
feature (an integer, currently 1, raised only for incompatible changes). Keys are not renamed or removed while the
feature exists. A missing key, or a missing file (as in the official releases), means the feature is absent.
Please read our contributing guidelines to see how you can contribute to this project.