├── .dockerignore
├── .editorconfig
├── .github
├── renovate.json
└── workflows
│ └── build.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── build.gradle.kts
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── schema.json
├── settings.gradle.kts
├── src
├── dist
│ └── plugins
│ │ └── README.txt
└── main
│ ├── kotlin
│ ├── br
│ │ └── com
│ │ │ └── felipezorzo
│ │ │ └── zpa
│ │ │ └── cli
│ │ │ ├── Arguments.kt
│ │ │ ├── DefaultRulesDefinition.kt
│ │ │ ├── InputFile.kt
│ │ │ ├── Main.kt
│ │ │ ├── config
│ │ │ └── ConfigFile.kt
│ │ │ ├── exporters
│ │ │ ├── ConsoleExporter.kt
│ │ │ ├── GenericIssueFormatExporter.kt
│ │ │ └── IssueExporter.kt
│ │ │ ├── plugin
│ │ │ ├── PluginManager.kt
│ │ │ ├── ZpaPluginDescriptor.kt
│ │ │ └── ZpaPluginDescriptorFinder.kt
│ │ │ ├── sonarreport
│ │ │ └── SonarPreviewReport.kt
│ │ │ ├── sqissue
│ │ │ ├── Duration.kt
│ │ │ └── GenericIssueData.kt
│ │ │ └── tracker
│ │ │ ├── Trackable.kt
│ │ │ ├── Tracker.kt
│ │ │ └── Tracking.kt
│ └── org
│ │ └── sonar
│ │ ├── api
│ │ └── server
│ │ │ └── rule
│ │ │ └── RulesDefinition.kt
│ │ └── plugins
│ │ └── plsqlopen
│ │ └── api
│ │ └── CustomPlSqlRulesDefinition.kt
│ └── resources
│ └── logging.properties
├── template
└── changelog.tpl
└── zpa-config-example.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | build/
2 | source-test/
3 | .gradle/
4 | !build/distributions/zpa-cli-*.tar
5 | !build/jreleaser/assemble/zpa-cli/jlink/zpa-cli-*-linux_musl-x86_64.tar.gz
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset=utf-8
5 | insert_final_newline=true
6 | indent_style=space
7 | indent_size=4
8 |
9 | [*.xml]
10 | indent_size=2
11 |
12 | [*.yml]
13 | indent_size=2
14 |
15 | [*.html]
16 | indent_size=2
17 |
18 | [*.properties]
19 | charset=latin1
20 |
21 | [*.sql]
22 | indent_size=2
23 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended",
5 | ":automergePatch",
6 | "schedule:weekly"
7 | ],
8 | "mode": "full"
9 | }
10 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - "*"
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | env:
12 | TAG: felipebz/zpa-cli:nightly
13 |
14 | jobs:
15 | build:
16 | name: Build and release
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 |
23 | - uses: actions/setup-java@v4
24 | with:
25 | distribution: temurin
26 | java-version: |
27 | 17
28 | 21
29 |
30 | - name: Setup Gradle
31 | uses: gradle/actions/setup-gradle@v4
32 |
33 | - name: Build with Gradle
34 | run: ./gradlew build --refresh-dependencies
35 |
36 | - name: Assemble
37 | run: ./gradlew copyJdksToCache jreleaserAssemble
38 |
39 | - name: Set up Docker Buildx
40 | uses: docker/setup-buildx-action@v3
41 |
42 | - name: Login to Docker Hub
43 | uses: docker/login-action@v3
44 | with:
45 | username: ${{ secrets.DOCKERHUB_USERNAME }}
46 | password: ${{ secrets.DOCKERHUB_TOKEN }}
47 |
48 | - name: Build Docker image
49 | uses: docker/build-push-action@v6
50 | with:
51 | context: .
52 | load: true
53 | tags: ${{ env.TAG }}
54 |
55 | - if: github.ref == 'refs/heads/main'
56 | name: Push docker image to Docker Hub
57 | uses: docker/build-push-action@v6
58 | with:
59 | context: .
60 | platforms: linux/amd64,linux/arm64/v8
61 | push: true
62 | tags: ${{ env.TAG }}
63 |
64 | - if: github.ref == 'refs/heads/main'
65 | name: Release artifacts
66 | run: ./gradlew jreleaserFullRelease
67 | env:
68 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 |
70 | demo:
71 | name: Analyze demo project on SonarCloud
72 | runs-on: ubuntu-latest
73 | needs: build
74 | if: github.ref == 'refs/heads/main'
75 | steps:
76 | - uses: actions/checkout@v4
77 | with:
78 | repository: utPLSQL/utPLSQL
79 |
80 | - name: Analyze with zpa-cli
81 | run: |
82 | docker run --rm \
83 | -v $PWD:/src \
84 | felipebz/zpa-cli:nightly \
85 | --sources . \
86 | --output-file zpa-issues.json \
87 | --output-format sq-generic-issue-import
88 |
89 | - name: Analyze with SonarCloud
90 | uses: sonarsource/sonarcloud-github-action@v5
91 | with:
92 | args: >
93 | -Dsonar.projectKey=utPLSQL-zpa-demo
94 | -Dsonar.organization=felipebz-github
95 | -Dsonar.externalIssuesReportPaths=zpa-issues.json
96 | -Dsonar.pullrequest.provider=
97 | -Dsonar.coverageReportPaths=
98 | -Dsonar.testExecutionReportPaths=
99 | -Dsonar.plsql.file.suffixes=sql,pkg,pks,pkb,fun,pcd,tgg,prc,tpb,trg,typ,tab,tps
100 | -Dsonar.scm.disabled=true
101 | -Dsonar.tests=
102 | env:
103 | SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
104 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /build/
3 | .idea/
4 | *.iml
5 | target/
6 | out/
7 | source-test/tools/
8 | zpa-issues.json
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies within all project spaces, and it also applies when
49 | an individual is representing the project or its community in public spaces.
50 | Examples of representing a project or community include using an official
51 | project e-mail address, posting via an official social media account, or acting
52 | as an appointed representative at an online or offline event. Representation of
53 | a project may be further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at felipe@felipezorzo.com.br. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing to our project! We appreciate your time and effort.
4 |
5 | ## How to report a bug or issue
6 |
7 | If you find a bug or issue, please report it by [opening an issue](https://github.com/felipebz/zpa-cli/issues/new) on GitHub. Please include as much detail as possible, including:
8 |
9 | - Steps to reproduce the issue
10 | - Expected behavior
11 | - Actual behavior
12 | - Screenshots (if applicable)
13 |
14 | ## How to suggest a new feature
15 |
16 | If you have an idea for a new feature, please [open an issue](https://github.com/felipebz/zpa-cli/issues/new) on GitHub. Please include as much detail as possible, including:
17 |
18 | - A description of the feature
19 | - Why you think it would be useful
20 | - Any potential drawbacks or limitations
21 |
22 | ## Pull Requests
23 |
24 | Currently, we are not currently accepting pull requests for new features. We appreciate your interest in contributing, but we have limited resources and cannot maintain additional features at this time.
25 |
26 | We will accept pull requests for minor bug fixes. Please include a detailed description of the bug and the steps to reproduce it.
27 |
28 | ## Code of Conduct
29 |
30 | Please note that we have a [code of conduct](CODE_OF_CONDUCT.md) in place to ensure that our community is welcoming and inclusive. Please read the code of conduct before contributing.
31 |
32 | ## License
33 |
34 | By contributing to this project, you agree to license your contributions under the [LGPL-3.0 license](LICENSE).
35 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM eclipse-temurin:21.0.7_6-jdk-alpine AS jre-build
2 |
3 | RUN "$JAVA_HOME"/bin/jlink \
4 | --add-modules java.logging,java.xml \
5 | --strip-debug \
6 | --no-man-pages \
7 | --no-header-files \
8 | --output /javaruntime
9 |
10 | FROM alpine:3.21
11 | ENV JAVA_HOME=/opt/java/openjdk
12 | ENV PATH="${JAVA_HOME}/bin:${PATH}"
13 |
14 | RUN addgroup -S -g 1001 zpa-cli && adduser -S -D -u 1001 -G zpa-cli zpa-cli
15 |
16 | COPY --from=jre-build /javaruntime $JAVA_HOME
17 |
18 | ADD build/distributions/zpa-cli-*.tar /opt/
19 |
20 | RUN set -eux; \
21 | mv /opt/zpa-cli-*/ /opt/zpa-cli/; \
22 | chown -R zpa-cli:zpa-cli /opt
23 |
24 | ENV PATH=/opt/zpa-cli/bin:$PATH
25 |
26 | USER zpa-cli
27 |
28 | WORKDIR /src
29 |
30 | ENTRYPOINT ["zpa-cli"]
31 |
32 | CMD [ "zpa-cli", "--help" ]
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ZPA CLI
2 |
3 | [](https://github.com/felipebz/zpa-cli/actions/workflows/build.yml)
4 |
5 | This is a command-line interface to the [Z PL/SQL Analyzer](https://github.com/felipebz/zpa). It is a code analyzer for Oracle PL/SQL and Oracle Forms projects.
6 |
7 | ## Downloading
8 |
9 | Official releases are available for download on the ["Releases" page](https://github.com/felipebz/zpa-cli/releases).
10 |
11 | ## Requirements
12 |
13 | * Java 17 or newer
14 |
15 | ## Usage
16 |
17 | Currently, the zpa-cli supports these options:
18 |
19 | * `--sources`: **[required]** Path to the folder containing the files to be analyzed.
20 | * `--forms-metadata`: Path to the Oracle Forms [metadata file](https://github.com/felipebz/zpa/wiki/Oracle-Forms-support).
21 | * `--extensions`: File extensions to analyze, separated by comma. The default value is `sql,pkg,pks,pkb,fun,pcd,tgg,prc,tpb,trg,typ,tab,tps`.
22 | * `--output-format`: Format of the output. The default value is `console`.
23 | * `--output-file`: Path to the output file.
24 | * `--config`: Path to the configuration file. The file format must comply with the [provided JSON schema](schema.json).
25 | You can refer to the example [zpa-config-example.json](zpa-config-example.json) for guidance. If the configuration
26 | file is not provided, only the rules marked as "activated by default" will be executed.
27 |
28 | Output formats:
29 | * `console`: writes the analysis result on the standard output
30 | * `sq-generic-issue-import`: generates a XML file using the ["Generic Issue Data" format](https://docs.sonarqube.org/latest/analysis/generic-issue/) that can be used in SonarCloud or in a SonarQube server (as an alternative to the dedicated [Z PL/SQL Analyzer Plugin](https://github.com/felipebz/zpa)).
31 |
32 | ### Example
33 |
34 | Running an analysis:
35 |
36 | `./zpa-cli/bin/zpa-cli --sources . --output-file zpa-issues.json --output-format sq-generic-issue-import`
37 |
38 | Then you can send the results to a SonarCloud or SonarQube server setting the `sonar.externalIssuesReportPaths` property:
39 |
40 | ```
41 | sonar-scanner
42 | -Dsonar.organization=$SONARCLOUD_ORGANIZATION \
43 | -Dsonar.projectKey=myproject \
44 | -Dsonar.sources=. \
45 | -Dsonar.host.url=https://sonarcloud.io \
46 | -Dsonar.externalIssuesReportPaths=zpa-issues.json
47 | ```
48 |
49 | Check the [demo project on SonarCloud](https://sonarcloud.io/project/issues?id=utPLSQL-zpa-demo&resolved=false)!
50 |
51 | ## Contributing
52 |
53 | Please read our [contributing guidelines](CONTRIBUTING.md) to see how you can contribute to this project.
54 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 | import org.jreleaser.model.api.common.ArchiveOptions
3 |
4 | group = "com.felipebz.zpa"
5 | version = "2.2.0"
6 |
7 | plugins {
8 | `maven-publish`
9 | kotlin("jvm") version "2.1.21"
10 | application
11 | id("org.jreleaser") version "1.17.0"
12 | id("org.jreleaser.jdks") version "1.17.0"
13 | }
14 |
15 | java {
16 | toolchain {
17 | languageVersion.set(JavaLanguageVersion.of(17))
18 | }
19 | }
20 |
21 | kotlin {
22 | compilerOptions {
23 | jvmTarget.set(JvmTarget.JVM_17)
24 | }
25 | }
26 |
27 | repositories {
28 | mavenLocal()
29 | mavenCentral()
30 | maven {
31 | setUrl("https://s01.oss.sonatype.org/content/repositories/snapshots/")
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation("org.jcommander:jcommander:2.0")
37 | implementation("com.felipebz.zpa:zpa-core:3.6.0")
38 | implementation("com.felipebz.zpa:zpa-checks:3.6.0")
39 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.4")
40 | implementation("org.pf4j:pf4j:3.13.0")
41 | implementation("org.slf4j:slf4j-jdk14:2.0.17")
42 | implementation("me.lucko:jar-relocator:1.7")
43 | testImplementation(kotlin("test"))
44 | }
45 |
46 | application {
47 | mainClass.set("br.com.felipezorzo.zpa.cli.MainKt")
48 | }
49 |
50 | publishing {
51 | repositories {
52 | maven {
53 | val releaseRepo = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
54 | val snapshotRepo = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
55 | url = if (project.version.toString().endsWith("SNAPSHOT")) snapshotRepo else releaseRepo
56 | credentials {
57 | username = project.findProperty("ossrh.user") as String? ?: System.getenv("OSSRH_USERNAME")
58 | password = project.findProperty("ossrh.password") as String? ?: System.getenv("OSSRH_PASSWORD")
59 | }
60 | }
61 | }
62 | publications {
63 | create("maven") {
64 | artifactId = "zpa-cli"
65 | from(components["java"])
66 | artifact(tasks["distZip"])
67 | versionMapping {
68 | usage("java-api") {
69 | fromResolutionOf("runtimeClasspath")
70 | }
71 | usage("java-runtime") {
72 | fromResolutionResult()
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | data class Jdk(val arch: String, val os: String, val extension: String, val checksum: String, val platform: String = os)
80 |
81 | val baseJdkUrl = "https://github.com/adoptium/temurin21-binaries/releases/download"
82 | val jdkBuild = "21.0.4+7"
83 | val jdkVersion = jdkBuild.split('.', '+').first()
84 | val jdkBuildFilename = jdkBuild.replace('+', '_')
85 | val jdksToBuild = listOf(
86 | Jdk(
87 | arch = "x64",
88 | os = "linux",
89 | extension = "tar.gz",
90 | checksum = "51fb4d03a4429c39d397d3a03a779077159317616550e4e71624c9843083e7b9"
91 | ),
92 |
93 | Jdk(
94 | arch = "aarch64",
95 | os = "linux",
96 | extension = "tar.gz",
97 | checksum = "d768eecddd7a515711659e02caef8516b7b7177fa34880a56398fd9822593a79"
98 | ),
99 |
100 | Jdk(
101 | arch = "x64",
102 | os = "mac",
103 | extension = "tar.gz",
104 | checksum = "e368e5de7111aa88e6bbabeff6f4c040772b57fb279cc4e197b51654085bbc18",
105 | platform = "osx"
106 | ),
107 |
108 | Jdk(
109 | arch = "aarch64",
110 | os = "mac",
111 | extension = "tar.gz",
112 | checksum = "dcf69a21601d9b1b25454bbad4f0f32784bb42cdbe4063492e15a851b74cb61e",
113 | platform = "osx"
114 | ),
115 |
116 | Jdk(
117 | arch = "x64",
118 | os = "windows",
119 | extension = "zip",
120 | checksum = "c725540d911531c366b985e5919efc8a73dd4030965cd9a740c3d2cd92c72c74"
121 | ),
122 |
123 | Jdk(
124 | arch = "x64",
125 | os = "alpine-linux",
126 | extension = "tar.gz",
127 | checksum = "8fa232fc9de5a861c1a6b0cbdc861d0b0a2bdbdd27da53d991802a460a7f0973",
128 | platform = "linux_musl"
129 | ),
130 | )
131 |
132 | jdks {
133 | jdksToBuild.forEach {
134 | create("${it.platform}_${it.arch}") {
135 | platform.set(it.platform)
136 | url.set("$baseJdkUrl/jdk-$jdkBuild/OpenJDK${jdkVersion}U-jdk_${it.arch}_${it.os}_hotspot_$jdkBuildFilename.${it.extension}")
137 | checksum.set(it.checksum)
138 | }
139 | }
140 | }
141 |
142 | jreleaser {
143 | project {
144 | description.set("The command-line interface of the Z PL/SQL Analyzer.")
145 | authors.set(listOf("felipebz"))
146 | license.set("LGPL-3.0")
147 | links {
148 | homepage.set("https://zpa.felipebz.com")
149 | }
150 | inceptionYear.set("2019")
151 | snapshot {
152 | fullChangelog.set(true)
153 | }
154 | }
155 | assemble {
156 | jlink {
157 | create("zpa-cli") {
158 | active.set(org.jreleaser.model.Active.ALWAYS)
159 | exported.set(true)
160 | stereotype.set(org.jreleaser.model.Stereotype.CLI)
161 | imageName.set("{{distributionName}}-{{projectVersion}}")
162 | moduleNames.set(listOf("java.logging", "java.xml"))
163 | jdeps {
164 | multiRelease.set("base")
165 | ignoreMissingDeps.set(true)
166 | }
167 | jdksToBuild.forEach {
168 | targetJdk {
169 | val jreleaserOs = it.platform
170 | val jreleaseArch = when (it.arch) {
171 | "aarch64" -> "aarch_64"
172 | "x64" -> "x86_64"
173 | else -> ""
174 | }
175 | val additionalDir = if (it.os == "mac") "/Contents/Home" else ""
176 | path.set(file("build/jdks/${it.platform}_${it.arch}/jdk-$jdkBuild$additionalDir"))
177 | platform.set("$jreleaserOs-$jreleaseArch")
178 | extraProperties.put("archiveFormat", if (jreleaserOs == "windows") "ZIP" else "TAR_GZ")
179 | options {
180 | longFileMode.set(ArchiveOptions.TarMode.POSIX)
181 | }
182 | }
183 | }
184 | jdk {
185 | val jdkPath = javaToolchains.launcherFor {
186 | languageVersion.set(JavaLanguageVersion.of(21))
187 | }.get().metadata.installationPath
188 |
189 | path.set(file(jdkPath))
190 | }
191 | javaArchive {
192 | path = "build/distributions/zpa-cli-{{projectVersion}}.tar"
193 | }
194 | fileSet {
195 | input = "src/dist/plugins"
196 | output = "plugins"
197 | includes = listOf("*")
198 | }
199 | }
200 | }
201 | }
202 | release {
203 | github {
204 | overwrite.set(true)
205 | tagName.set("{{projectVersion}}")
206 | draft.set(true)
207 | changelog {
208 | formatted.set(org.jreleaser.model.Active.ALWAYS)
209 | preset.set("conventional-commits")
210 | contentTemplate.set(file("template/changelog.tpl"))
211 | contributors {
212 | enabled.set(false)
213 | }
214 | hide {
215 | uncategorized.set(true)
216 | }
217 | }
218 | }
219 | }
220 | distributions {
221 | create("zpa-cli") {
222 | artifact {
223 | path.set(file("build/distributions/{{distributionName}}-{{projectVersion}}.zip"))
224 | }
225 | }
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.parallel=true
2 | org.gradle.caching=true
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/felipebz/zpa-cli/52a2e0b6f6e6b0cd5980f6551919bf52c8f2c121/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json-schema.org/draft/2020-12/schema",
3 | "$defs": {
4 | "BaseRuleCategory": {
5 | "type": "string",
6 | "enum": [
7 | "default",
8 | "none"
9 | ]
10 | },
11 | "RuleLevel": {
12 | "type": "string",
13 | "enum": [
14 | "on",
15 | "off",
16 | "info",
17 | "minor",
18 | "major",
19 | "critical",
20 | "blocker"
21 | ]
22 | },
23 | "RuleOptions": {
24 | "type": "object",
25 | "properties": {
26 | "level": {
27 | "$ref": "#/$defs/RuleLevel"
28 | },
29 | "parameters": {
30 | "additionalProperties": {
31 | "type": "string"
32 | }
33 | }
34 | }
35 | }
36 | },
37 | "type": "object",
38 | "properties": {
39 | "base": {
40 | "$ref": "#/$defs/BaseRuleCategory"
41 | },
42 | "rules": {
43 | "additionalProperties": {
44 | "oneOf": [
45 | {
46 | "$ref": "#/$defs/RuleLevel"
47 | },
48 | {
49 | "$ref": "#/$defs/RuleOptions"
50 | }
51 | ]
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "zpa-cli"
--------------------------------------------------------------------------------
/src/dist/plugins/README.txt:
--------------------------------------------------------------------------------
1 | Put your custom plugins in this directory.
2 |
3 | Follow the instructions at https://github.com/felipebz/zpa/wiki/Create-a-plugin-with-custom-rules to create a custom plugin with additional rules.
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/Arguments.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli
2 |
3 | import com.beust.jcommander.Parameter
4 |
5 | class Arguments {
6 | @Parameter(names = ["--sources"], description = "Folder with files", required = true)
7 | var sources: String = ""
8 |
9 | @Parameter(names = ["--forms-metadata"], description = "Oracle Forms metadata file")
10 | var formsMetadata: String = ""
11 |
12 | @Parameter(names = ["--extensions"], description = "Extensions to analyze")
13 | var extensions: String = "sql,pkg,pks,pkb,fun,pcd,tgg,prc,tpb,trg,typ,tab,tps"
14 |
15 | @Parameter(names = ["--output-format"], description = "Format of the output file")
16 | var outputFormat: String = "console"
17 |
18 | @Parameter(names = ["--output-file"], description = "Output filename")
19 | var outputFile: String = ""
20 |
21 | @Parameter(names = ["--config"], description = "Config file")
22 | var configFile: String = ""
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/DefaultRulesDefinition.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli
2 |
3 | import org.sonar.plsqlopen.checks.CheckList
4 | import org.sonar.plugins.plsqlopen.api.ZpaRulesDefinition
5 |
6 | class DefaultRulesDefinition : ZpaRulesDefinition {
7 | override fun checkClasses(): Array> {
8 | return CheckList.checks.toTypedArray()
9 | }
10 |
11 | override fun repositoryKey(): String {
12 | return "zpa"
13 | }
14 |
15 | override fun repositoryName(): String {
16 | return "Default"
17 | }
18 | }
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/InputFile.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli
2 |
3 | import org.sonar.plugins.plsqlopen.api.PlSqlFile
4 | import java.io.File
5 | import java.nio.charset.Charset
6 | import java.nio.file.Path
7 | import java.nio.file.Paths
8 | import kotlin.io.path.invariantSeparatorsPathString
9 |
10 | class InputFile(private val type: PlSqlFile.Type,
11 | baseDirPath: Path,
12 | private val file: File,
13 | private val charset: Charset) : PlSqlFile {
14 |
15 | override fun contents(): String =
16 | file.inputStream().use {
17 | return it.bufferedReader(charset).use { r -> r.readText() }
18 | }
19 |
20 | override fun fileName(): String = file.name
21 |
22 | override fun type(): PlSqlFile.Type = type
23 |
24 | override fun path(): Path = file.toPath()
25 |
26 | val pathRelativeToBase: String = baseDirPath.relativize(Paths.get(file.absolutePath)).invariantSeparatorsPathString
27 |
28 | override fun hashCode(): Int {
29 | return file.hashCode()
30 | }
31 |
32 | override fun equals(other: Any?): Boolean {
33 | if (other == null || other !is InputFile) return false
34 | return file == other.file
35 | }
36 |
37 | override fun toString(): String {
38 | return pathRelativeToBase
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/Main.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli
2 |
3 | import br.com.felipezorzo.zpa.cli.config.BaseRuleCategory
4 | import br.com.felipezorzo.zpa.cli.config.ConfigFile
5 | import br.com.felipezorzo.zpa.cli.config.RuleConfiguration
6 | import br.com.felipezorzo.zpa.cli.config.RuleLevel
7 | import br.com.felipezorzo.zpa.cli.exporters.ConsoleExporter
8 | import br.com.felipezorzo.zpa.cli.exporters.GenericIssueFormatExporter
9 | import br.com.felipezorzo.zpa.cli.plugin.PluginManager
10 | import com.beust.jcommander.JCommander
11 | import com.beust.jcommander.ParameterException
12 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
13 | import me.lucko.jarrelocator.JarRelocator
14 | import me.lucko.jarrelocator.Relocation
15 | import org.sonar.plsqlopen.CustomAnnotationBasedRulesDefinition
16 | import org.sonar.plsqlopen.metadata.FormsMetadata
17 | import org.sonar.plsqlopen.rules.ActiveRules
18 | import org.sonar.plsqlopen.rules.Repository
19 | import org.sonar.plsqlopen.rules.RuleMetadataLoader
20 | import org.sonar.plsqlopen.rules.ZpaChecks
21 | import org.sonar.plsqlopen.squid.AstScanner
22 | import org.sonar.plsqlopen.squid.ProgressReport
23 | import org.sonar.plsqlopen.utils.log.Loggers
24 | import org.sonar.plugins.plsqlopen.api.PlSqlFile
25 | import org.sonar.plugins.plsqlopen.api.ZpaRulesDefinition
26 | import org.sonar.plugins.plsqlopen.api.checks.PlSqlVisitor
27 | import java.io.File
28 | import java.io.IOException
29 | import java.nio.charset.StandardCharsets
30 | import java.nio.file.Files
31 | import java.nio.file.Path
32 | import java.util.*
33 | import java.util.concurrent.TimeUnit
34 | import java.util.logging.LogManager
35 | import java.util.stream.Collectors
36 | import kotlin.io.path.absolute
37 | import kotlin.io.path.extension
38 | import kotlin.io.path.listDirectoryEntries
39 | import kotlin.io.path.name
40 | import kotlin.system.measureTimeMillis
41 |
42 | const val CONSOLE = "console"
43 | const val GENERIC_ISSUE_FORMAT = "sq-generic-issue-import"
44 |
45 | class Main(private val args: Arguments) {
46 |
47 | val mapper = jacksonObjectMapper()
48 |
49 | fun run() {
50 | javaClass.getResourceAsStream("/logging.properties").use {
51 | LogManager.getLogManager().readConfiguration(it)
52 | }
53 |
54 | val codePath = Path.of(Main::class.java.protectionDomain.codeSource.location.toURI())
55 | val appHome = if (codePath.extension == "jar" && (codePath.parent.name == "lib" || codePath.parent.name == "jars")) {
56 | codePath.parent.parent.absolute()
57 | } else {
58 | Path.of(".")
59 | }
60 |
61 | val tempDir = Files.createTempDirectory("zpa-cli")
62 | tempDir.toFile().deleteOnExit()
63 |
64 | val pluginRoot = appHome.resolve("plugins")
65 | pluginRoot.listDirectoryEntries("*.jar").forEach {
66 | val input = it.toFile()
67 | val output = tempDir.resolve(it.fileName).toFile()
68 | output.deleteOnExit()
69 |
70 | val rules: MutableList = ArrayList()
71 | rules.add(Relocation("org.sonar.plugins.plsqlopen.api.sslr", "com.felipebz.flr.api"))
72 |
73 | val relocator = JarRelocator(input, output, rules)
74 | try {
75 | relocator.run()
76 | } catch (e: IOException) {
77 | throw RuntimeException("Unable to relocate", e)
78 | }
79 | }
80 |
81 | val pluginManager = PluginManager(tempDir)
82 | pluginManager.loadPlugins()
83 | pluginManager.startPlugins()
84 |
85 | // print loaded plugins
86 | for (plugin in pluginManager.startedPlugins) {
87 | LOG.info("Plugin '${plugin.descriptor.pluginId}@${plugin.descriptor.version}' loaded")
88 | }
89 |
90 | val extensions = args.extensions.split(',')
91 |
92 | val issueExporter = when (args.outputFormat) {
93 | CONSOLE -> ConsoleExporter()
94 | GENERIC_ISSUE_FORMAT -> GenericIssueFormatExporter(args.outputFile)
95 | else -> throw IllegalArgumentException("Invalid output format")
96 | }
97 |
98 | val ellapsedTime = measureTimeMillis {
99 |
100 | val baseDir = File(args.sources).absoluteFile
101 | val baseDirPath = baseDir.toPath()
102 |
103 | val activeRules = getActiveRules()
104 |
105 | val ruleMetadataLoader = RuleMetadataLoader()
106 |
107 | val checkList = mutableListOf()
108 |
109 | val rulesDefinitions = listOf(
110 | DefaultRulesDefinition(),
111 | *pluginManager.getExtensions(ZpaRulesDefinition::class.java).toTypedArray()
112 | )
113 |
114 | for (rulesDefinition in rulesDefinitions) {
115 | val repository = Repository(rulesDefinition.repositoryKey())
116 | CustomAnnotationBasedRulesDefinition.load(
117 | repository, "plsqlopen",
118 | rulesDefinition.checkClasses().toList(), ruleMetadataLoader
119 | )
120 |
121 | activeRules.addRepository(repository)
122 |
123 | val checks = ZpaChecks(activeRules, repository.key, ruleMetadataLoader)
124 | .addAnnotatedChecks(rulesDefinition.checkClasses().toList())
125 |
126 | checkList.addAll(checks.all())
127 | }
128 |
129 | val files = baseDir
130 | .walkTopDown()
131 | .filter { it.isFile && extensions.contains(it.extension.lowercase(Locale.getDefault())) }
132 | .map { InputFile(PlSqlFile.Type.MAIN, baseDirPath, it, StandardCharsets.UTF_8) }
133 | .toList()
134 |
135 | val metadata = FormsMetadata.loadFromFile(args.formsMetadata)
136 |
137 | val progressReport = ProgressReport("Report about progress of code analyzer", TimeUnit.SECONDS.toMillis(10))
138 | progressReport.start(files.map { it.pathRelativeToBase }.toList())
139 |
140 | val scanner = AstScanner(checkList, metadata, true, StandardCharsets.UTF_8)
141 |
142 | val issues = files.parallelStream().flatMap { file ->
143 | val scannerResult = scanner.scanFile(file)
144 | progressReport.nextFile()
145 | scannerResult.issues.stream()
146 | }.collect(Collectors.toList())
147 |
148 | progressReport.stop()
149 |
150 | issueExporter.export(issues)
151 | }
152 |
153 | LOG.info("Time elapsed: $ellapsedTime ms")
154 | pluginManager.stopPlugins()
155 | pluginManager.unloadPlugins()
156 | }
157 |
158 | private fun getActiveRules(): ActiveRules {
159 | val activeRules = ActiveRules()
160 | val config = if (args.configFile.isNotEmpty()) {
161 | val configFile = File(args.configFile)
162 | mapper.readValue(configFile, ConfigFile::class.java)
163 | } else {
164 | ConfigFile()
165 | }
166 |
167 | if (config.rules.isNotEmpty()) {
168 | activeRules.addRuleConfigurer { repo, rule, configuration ->
169 | var ruleConfig = config.rules["${repo.key}:${rule.key}"] ?: config.rules[rule.key]
170 | if (config.base == BaseRuleCategory.DEFAULT && rule.isActivatedByDefault) {
171 | ruleConfig = ruleConfig ?: RuleConfiguration()
172 | }
173 |
174 | if (ruleConfig == null || ruleConfig.options.level == RuleLevel.OFF) {
175 | return@addRuleConfigurer false
176 | }
177 |
178 | if (ruleConfig.options.level != RuleLevel.ON) {
179 | configuration.severity = ruleConfig.options.level.toString()
180 | }
181 | configuration.parameters.putAll(ruleConfig.options.parameters)
182 | true
183 | }
184 | }
185 | return activeRules
186 | }
187 |
188 | companion object {
189 | val LOG = Loggers.getLogger(Main::class.java)
190 | }
191 | }
192 |
193 | fun main(args: Array) {
194 | val arguments = Arguments()
195 | val cmd = JCommander.newBuilder()
196 | .addObject(arguments)
197 | .programName("map-generator")
198 | .build()
199 | try {
200 | cmd.parse(*args)
201 | Main(arguments).run()
202 | } catch (exception: ParameterException) {
203 | println(exception.message)
204 | cmd.usage()
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/config/ConfigFile.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.config
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties
4 | import com.fasterxml.jackson.annotation.JsonProperty
5 | import com.fasterxml.jackson.core.JsonGenerator
6 | import com.fasterxml.jackson.core.JsonParser
7 | import com.fasterxml.jackson.databind.*
8 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize
9 | import com.fasterxml.jackson.databind.annotation.JsonSerialize
10 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
11 |
12 | @JsonIgnoreProperties(value = ["\$schema"])
13 | data class ConfigFile(
14 | val base: BaseRuleCategory = BaseRuleCategory.DEFAULT,
15 | val rules: Map = emptyMap(),
16 | )
17 |
18 | enum class BaseRuleCategory {
19 | @JsonProperty("default")
20 | DEFAULT,
21 | @JsonProperty("none")
22 | NONE
23 | }
24 |
25 | @JsonDeserialize(using = RuleCategoryDeserializer::class)
26 | @JsonSerialize(using = RuleCategorySerializer::class)
27 | class RuleConfiguration {
28 | var options: RuleOptions = RuleOptions()
29 | }
30 |
31 | enum class RuleLevel {
32 | @JsonProperty("on")
33 | ON,
34 | @JsonProperty("off")
35 | OFF,
36 | @JsonProperty("blocker")
37 | BLOCKER,
38 | @JsonProperty("critical")
39 | CRITICAL,
40 | @JsonProperty("major")
41 | MAJOR,
42 | @JsonProperty("minor")
43 | MINOR,
44 | @JsonProperty("info")
45 | INFO
46 | }
47 |
48 | class RuleOptions {
49 | var level: RuleLevel = RuleLevel.ON
50 | var parameters: Map = emptyMap()
51 | }
52 |
53 | class RuleCategoryDeserializer : JsonDeserializer() {
54 | override fun deserialize(p: JsonParser, ctxt: DeserializationContext): RuleConfiguration {
55 | val node: JsonNode = p.codec.readTree(p)
56 | val ruleConfiguration = RuleConfiguration()
57 | val mapper = jacksonObjectMapper()
58 |
59 | if (node.isTextual) {
60 | ruleConfiguration.options.level = RuleLevel.valueOf(node.asText().uppercase())
61 | } else if (node.isObject) {
62 | ruleConfiguration.options = mapper.treeToValue(node, RuleOptions::class.java)
63 | }
64 |
65 | return ruleConfiguration
66 | }
67 | }
68 |
69 | class RuleCategorySerializer : JsonSerializer() {
70 | override fun serialize(value: RuleConfiguration, gen: JsonGenerator, serializers: SerializerProvider) {
71 | val mapper = jacksonObjectMapper()
72 |
73 | if (value.options.parameters.isEmpty()) {
74 | gen.writeString(value.options.level.toString())
75 | } else {
76 | gen.writeTree(mapper.valueToTree(value.options))
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/exporters/ConsoleExporter.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.exporters
2 |
3 | import br.com.felipezorzo.zpa.cli.InputFile
4 | import org.sonar.plsqlopen.squid.ZpaIssue
5 |
6 | class ConsoleExporter : IssueExporter {
7 | override fun export(issues: List) {
8 | for ((file, fileIssues) in issues.groupBy { (it.file as InputFile).pathRelativeToBase }.toSortedMap()) {
9 | println("File: $file")
10 |
11 | for (issue in fileIssues.sortedWith(
12 | compareBy(
13 | { it.primaryLocation.startLine() },
14 | { it.primaryLocation.startLineOffset() })
15 | )) {
16 | val startLine = issue.primaryLocation.startLine()
17 | val startColumn = issue.primaryLocation.startLineOffset()
18 | val activeRule = issue.check.activeRule
19 | val severity = activeRule.severity
20 |
21 | var positionFormatted = "$startLine"
22 | if (startColumn != -1) {
23 | positionFormatted += ":$startColumn"
24 | }
25 | println("${positionFormatted.padEnd(10)}${severity.padEnd(10)}${issue.primaryLocation.message()}")
26 | }
27 |
28 | println("")
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/exporters/GenericIssueFormatExporter.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.exporters
2 |
3 | import br.com.felipezorzo.zpa.cli.InputFile
4 | import br.com.felipezorzo.zpa.cli.sqissue.*
5 | import com.fasterxml.jackson.databind.ObjectMapper
6 | import org.sonar.plsqlopen.squid.ZpaIssue
7 | import java.io.File
8 |
9 | class GenericIssueFormatExporter(private val outputFile: String) : IssueExporter {
10 | override fun export(issues: List) {
11 | val genericIssues = mutableListOf()
12 | for (issue in issues) {
13 | val relativeFilePathStr = (issue.file as InputFile).pathRelativeToBase
14 |
15 | val issuePrimaryLocation = issue.primaryLocation
16 |
17 | val primaryLocation = PrimaryLocation(
18 | issuePrimaryLocation.message(),
19 | relativeFilePathStr,
20 | createTextRange(
21 | issuePrimaryLocation.startLine(),
22 | issuePrimaryLocation.endLine(),
23 | issuePrimaryLocation.startLineOffset(),
24 | issuePrimaryLocation.endLineOffset()
25 | )
26 | )
27 |
28 | val secondaryLocations = mutableListOf()
29 |
30 | for (secondary in issue.secondaryLocations) {
31 | secondaryLocations += SecondaryLocation(
32 | secondary.message(),
33 | relativeFilePathStr,
34 | createTextRange(
35 | secondary.startLine(),
36 | secondary.endLine(),
37 | secondary.startLineOffset(),
38 | secondary.endLineOffset()
39 | )
40 | )
41 | }
42 |
43 | val activeRule = issue.check.activeRule
44 |
45 | val type = when {
46 | activeRule.tags.contains("vulnerability") -> "VULNERABILITY"
47 | activeRule.tags.contains("bug") -> "BUG"
48 | else -> "CODE_SMELL"
49 | }
50 |
51 | genericIssues += Issue(
52 | ruleId = activeRule.ruleKey.toString(),
53 | severity = activeRule.severity,
54 | type = type,
55 | primaryLocation = primaryLocation,
56 | duration = activeRule.remediationConstant,
57 | secondaryLocations = secondaryLocations
58 | )
59 | }
60 | val genericReport = GenericIssueData(genericIssues)
61 |
62 | val mapper = ObjectMapper()
63 | val generatedOutput = mapper.writeValueAsString(genericReport)
64 | val file = File(outputFile)
65 | file.parentFile?.mkdirs()
66 | file.writeText(generatedOutput)
67 | }
68 |
69 | private fun createTextRange(startLine: Int, endLine: Int, startLineOffset: Int, endLineOffset: Int): TextRange {
70 | return TextRange(
71 | startLine = startLine,
72 | endLine = if (endLine > -1) endLine else null,
73 | startColumn = if (startLineOffset > -1) startLineOffset else null,
74 | endColumn = if (endLineOffset > -1) endLineOffset else null
75 | )
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/exporters/IssueExporter.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.exporters
2 |
3 | import org.sonar.plsqlopen.squid.ZpaIssue
4 |
5 | fun interface IssueExporter {
6 | fun export(issues: List)
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/plugin/PluginManager.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.plugin
2 |
3 | import org.pf4j.DefaultPluginManager
4 | import org.pf4j.PluginDescriptorFinder
5 | import java.nio.file.Path
6 |
7 | class PluginManager(pluginRoot: Path): DefaultPluginManager(pluginRoot) {
8 |
9 | override fun createPluginDescriptorFinder(): PluginDescriptorFinder {
10 | return ZpaPluginDescriptorFinder()
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/plugin/ZpaPluginDescriptor.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.plugin
2 |
3 | import org.pf4j.DefaultPluginDescriptor
4 |
5 | class ZpaPluginDescriptor : DefaultPluginDescriptor() {
6 |
7 | public override fun setPluginId(pluginId: String): DefaultPluginDescriptor {
8 | return super.setPluginId(pluginId)
9 | }
10 |
11 | fun setVersion(version: String): DefaultPluginDescriptor {
12 | return super.setPluginVersion(version)
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/plugin/ZpaPluginDescriptorFinder.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.plugin
2 |
3 | import org.pf4j.PluginDescriptor
4 | import org.pf4j.PluginDescriptorFinder
5 | import org.pf4j.PluginRuntimeException
6 | import org.pf4j.util.FileUtils
7 | import java.io.IOException
8 | import java.nio.file.Files
9 | import java.nio.file.Path
10 | import java.util.jar.JarFile
11 | import java.util.jar.Manifest
12 |
13 | class ZpaPluginDescriptorFinder : PluginDescriptorFinder {
14 |
15 | override fun isApplicable(pluginPath: Path): Boolean {
16 | return Files.exists(pluginPath) && FileUtils.isJarFile(pluginPath)
17 | }
18 |
19 | override fun find(pluginPath: Path): PluginDescriptor {
20 | val manifest = readManifest(pluginPath)
21 | return createPluginDescriptor(manifest)
22 | }
23 |
24 | private fun readManifest(pluginPath: Path): Manifest {
25 | try {
26 | return JarFile(pluginPath.toFile()).use { it.manifest }
27 | } catch (e: IOException) {
28 | throw PluginRuntimeException(e, "Cannot read manifest from {}", pluginPath)
29 | }
30 | }
31 |
32 | private fun createPluginDescriptor(manifest: Manifest): PluginDescriptor {
33 | val pluginDescriptor = ZpaPluginDescriptor()
34 | val attributes = manifest.mainAttributes
35 |
36 | val id = attributes.getValue(PLUGIN_ID)
37 | pluginDescriptor.pluginId = id
38 |
39 | val version = attributes.getValue(PLUGIN_VERSION)
40 | if (version.isNotEmpty()) {
41 | pluginDescriptor.version = version
42 | }
43 |
44 | return pluginDescriptor
45 | }
46 |
47 | companion object {
48 | const val PLUGIN_ID = "Plugin-Key"
49 | const val PLUGIN_VERSION = "Plugin-Version"
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/sonarreport/SonarPreviewReport.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.sonarreport
2 |
3 | data class SonarPreviewReport(
4 | val components: List,
5 | val issues: List,
6 | val rules: List,
7 | val version: String
8 | )
9 |
10 | data class Component(
11 | val key: String,
12 | val path: String,
13 | val status: String
14 | )
15 |
16 | data class Issue(
17 | val assignee: String,
18 | val component: String,
19 | val creationDate: String,
20 | val endLine: Int,
21 | val endOffset: Int,
22 | val isNew: Boolean,
23 | val key: String,
24 | val line: Int,
25 | val message: String,
26 | val rule: String,
27 | val severity: String,
28 | val startLine: Int,
29 | val startOffset: Int,
30 | val status: String
31 | )
32 |
33 | data class Rule(
34 | val key: String,
35 | val name: String,
36 | val repository: String,
37 | val rule: String
38 | )
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/sqissue/Duration.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.sqissue
2 |
3 | import java.util.regex.Pattern
4 |
5 | object Duration {
6 |
7 | private const val HOURS_IN_DAY = 8
8 | private const val MINUTES_IN_HOUR = 60
9 | private const val DAY = "d"
10 | private const val HOUR = "h"
11 | private const val MINUTE = "min"
12 |
13 | private val pattern: Pattern =
14 | Pattern.compile("\\s*+(?:(\\d++)\\s*+$DAY)?+\\s*+(?:(\\d++)\\s*+$HOUR)?+\\s*+(?:(\\d++)\\s*+$MINUTE)?+\\s*+")
15 |
16 | fun toMinute(duration: String): Int {
17 | val matcher = pattern.matcher(duration)
18 |
19 | var days = 0
20 | var hours = 0
21 | var minutes = 0
22 |
23 | if (matcher.find()) {
24 | val daysDuration = matcher.group(1)
25 | if (daysDuration != null) {
26 | days = Integer.parseInt(daysDuration)
27 | }
28 | val hoursText = matcher.group(2)
29 | if (hoursText != null) {
30 | hours = Integer.parseInt(hoursText)
31 | }
32 | val minutesText = matcher.group(3)
33 | if (minutesText != null) {
34 | minutes = Integer.parseInt(minutesText)
35 | }
36 | }
37 |
38 | return (days * HOURS_IN_DAY * MINUTES_IN_HOUR) + (hours * MINUTES_IN_HOUR) + minutes
39 | }
40 | }
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/sqissue/GenericIssueData.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.sqissue
2 |
3 | data class GenericIssueData(
4 | val issues: List
5 | )
6 |
7 | data class Issue(
8 | val engineId: String = "zpa",
9 | val ruleId: String,
10 | val severity: String,
11 | val type: String,
12 | val primaryLocation: PrimaryLocation,
13 | private val duration: String,
14 | val secondaryLocations: List) {
15 | val effortMinutes: Int = Duration.toMinute(duration)
16 | }
17 |
18 | data class PrimaryLocation(
19 | val message: String,
20 | val filePath: String,
21 | val textRange: TextRange
22 | )
23 |
24 | data class SecondaryLocation(
25 | val message: String,
26 | val filePath: String,
27 | val textRange: TextRange
28 | )
29 |
30 | data class TextRange(
31 | val startLine: Int,
32 | val endLine: Int?,
33 | val startColumn: Int?,
34 | val endColumn: Int?
35 | )
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/tracker/Trackable.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.tracker
2 |
3 | interface Trackable {
4 | val ruleKey: String
5 | val message: String
6 | val line: Int
7 | val lineHash: String
8 | val textRangeHash: Int
9 | val serverIssueKey: String
10 | val path: String
11 | }
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/tracker/Tracker.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.tracker
2 |
3 | import java.util.*
4 |
5 | class Tracker {
6 | fun track(
7 | rawTrackableSupplier: Collection,
8 | baseTrackableSupplier: Collection
9 | ): Tracking {
10 | val tracking = Tracking(rawTrackableSupplier, baseTrackableSupplier)
11 |
12 | // 1. match issues with same server issue key
13 | match(tracking) { ServerIssueSearchKey(it) }
14 |
15 | // 2. match issues with same rule, same line and same text range hash, but not necessarily with same message
16 | match(tracking) { LineAndTextRangeHashKey(it) }
17 |
18 | // 3. match issues with same rule, same message and same text range hash
19 | match(tracking) { TextRangeHashAndMessageKey(it) }
20 |
21 | // 4. match issues with same rule, same line and same message
22 | match(tracking) { LineAndMessageKey(it) }
23 |
24 | // 5. match issues with same rule and same text range hash but different line and different message.
25 | // See SONAR-2812
26 | match(tracking) { TextRangeHashKey(it) }
27 |
28 | // 6. match issues with same rule, same line and same line hash
29 | match(tracking) { LineAndLineHashKey(it) }
30 |
31 | // 7. match issues with same rule and same same line hash
32 | match(tracking) { LineHashKey(it) }
33 | return tracking
34 | }
35 |
36 | private fun match(tracking: Tracking, factory: SearchKeyFactory) {
37 | if (tracking.isComplete) {
38 | return
39 | }
40 | val baseSearch: MutableMap> = HashMap()
41 | for (base in tracking.unmatchedBases) {
42 | val searchKey = factory.invoke(base)
43 | if (!baseSearch.containsKey(searchKey)) {
44 | baseSearch[searchKey] = ArrayList()
45 | }
46 | baseSearch[searchKey]!!.add(base)
47 | }
48 | for (raw in tracking.unmatchedRaws) {
49 | val rawKey = factory.invoke(raw)
50 | val bases: Collection? = baseSearch[rawKey]
51 | if (bases != null && !bases.isEmpty()) {
52 | val match = bases.iterator().next()
53 | tracking.match(raw, match)
54 | baseSearch[rawKey]!!.remove(match)
55 | }
56 | }
57 | }
58 |
59 | private interface SearchKey
60 |
61 | private fun interface SearchKeyFactory : (Trackable) -> SearchKey
62 |
63 | private class LineAndTextRangeHashKey constructor(trackable: Trackable) :
64 | SearchKey {
65 | private val ruleKey = trackable.ruleKey
66 | private val textRangeHash = trackable.textRangeHash
67 | private val line = trackable.line
68 |
69 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
70 | override fun equals(other: Any?): Boolean {
71 | val that = other as LineAndTextRangeHashKey?
72 | // start with most discriminant field
73 | return (that != null
74 | && Objects.equals(line, that.line)
75 | && Objects.equals(textRangeHash, that.textRangeHash)
76 | && ruleKey == that.ruleKey)
77 | }
78 |
79 | override fun hashCode(): Int {
80 | var result = ruleKey.hashCode()
81 | result = 31 * result + textRangeHash.hashCode()
82 | result = 31 * result + line.hashCode()
83 | return result
84 | }
85 | }
86 |
87 | private class LineAndLineHashKey(trackable: Trackable) :
88 | SearchKey {
89 | private val ruleKey = trackable.ruleKey
90 | private val line = trackable.line
91 | private val lineHash = trackable.lineHash
92 |
93 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
94 | override fun equals(other: Any?): Boolean {
95 | val that = other as LineAndLineHashKey?
96 | // start with most discriminant field
97 | return that != null
98 | && (Objects.equals(line, that.line)
99 | && Objects.equals(lineHash, that.lineHash)
100 | && ruleKey == that.ruleKey)
101 | }
102 |
103 | override fun hashCode(): Int {
104 | var result = ruleKey.hashCode()
105 | result = 31 * result + lineHash.hashCode()
106 | result = 31 * result + line.hashCode()
107 | return result
108 | }
109 | }
110 |
111 | private class LineHashKey(trackable: Trackable) :
112 | SearchKey {
113 | private val ruleKey = trackable.ruleKey
114 | private val lineHash = trackable.lineHash
115 |
116 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
117 | override fun equals(other: Any?): Boolean {
118 | val that = other as LineHashKey?
119 | // start with most discriminant field
120 | return that != null
121 | && (Objects.equals(lineHash, that.lineHash)
122 | && ruleKey == that.ruleKey)
123 | }
124 |
125 | override fun hashCode(): Int {
126 | var result = ruleKey.hashCode()
127 | result = 31 * result + lineHash.hashCode()
128 | return result
129 | }
130 | }
131 |
132 | private class TextRangeHashAndMessageKey(trackable: Trackable) :
133 | SearchKey {
134 | private val ruleKey = trackable.ruleKey
135 | private val message = trackable.message
136 | private val textRangeHash = trackable.textRangeHash
137 |
138 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
139 | override fun equals(other: Any?): Boolean {
140 | val that = other as TextRangeHashAndMessageKey?
141 | // start with most discriminant field
142 | return that != null
143 | && (Objects.equals(textRangeHash, that.textRangeHash)
144 | && message == that.message && ruleKey == that.ruleKey)
145 | }
146 |
147 | override fun hashCode(): Int {
148 | var result = ruleKey.hashCode()
149 | result = 31 * result + message.hashCode()
150 | result = 31 * result + textRangeHash.hashCode()
151 | return result
152 | }
153 | }
154 |
155 | private class LineAndMessageKey(trackable: Trackable) :
156 | SearchKey {
157 | private val ruleKey = trackable.ruleKey
158 | private val message = trackable.message
159 | private val line = trackable.line
160 |
161 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
162 | override fun equals(other: Any?): Boolean {
163 | val that = other as LineAndMessageKey?
164 | // start with most discriminant field
165 | return that != null
166 | && (Objects.equals(line, that.line)
167 | && message == that.message && ruleKey == that.ruleKey)
168 | }
169 |
170 | override fun hashCode(): Int {
171 | var result = ruleKey.hashCode()
172 | result = 31 * result + message.hashCode()
173 | result = 31 * result + line.hashCode()
174 | return result
175 | }
176 | }
177 |
178 | private class TextRangeHashKey constructor(trackable: Trackable) :
179 | SearchKey {
180 | private val ruleKey = trackable.ruleKey
181 | private val textRangeHash = trackable.textRangeHash
182 |
183 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
184 | override fun equals(other: Any?): Boolean {
185 | val that = other as TextRangeHashKey?
186 | // start with most discriminant field
187 | return that != null
188 | && (Objects.equals(textRangeHash, that.textRangeHash)
189 | && ruleKey == that.ruleKey)
190 | }
191 |
192 | override fun hashCode(): Int {
193 | var result = ruleKey.hashCode()
194 | result = 31 * result + textRangeHash.hashCode()
195 | return result
196 | }
197 | }
198 |
199 | private class ServerIssueSearchKey(trackable: Trackable) :
200 | SearchKey {
201 | private val serverIssueKey: String = trackable.serverIssueKey
202 |
203 | // note: the design of the enclosing caller ensures that 'o' is of the correct class and not null
204 | override fun equals(other: Any?): Boolean {
205 | val that = other as ServerIssueSearchKey?
206 | return that != null
207 | && !isBlank(serverIssueKey)
208 | && !isBlank(that.serverIssueKey)
209 | && serverIssueKey == that.serverIssueKey
210 | }
211 |
212 | override fun hashCode(): Int {
213 | return serverIssueKey.hashCode()
214 | }
215 |
216 | private fun isBlank(s: String?): Boolean {
217 | return s == null || s.isEmpty()
218 | }
219 | }
220 | }
--------------------------------------------------------------------------------
/src/main/kotlin/br/com/felipezorzo/zpa/cli/tracker/Tracking.kt:
--------------------------------------------------------------------------------
1 | package br.com.felipezorzo.zpa.cli.tracker
2 |
3 | import java.util.IdentityHashMap
4 |
5 | class Tracking(
6 | private val raws: Collection,
7 | private val bases: Collection
8 | ) {
9 | /**
10 | * Matched issues -> a raw issue is associated to a base issue
11 | */
12 | private val rawToBase = IdentityHashMap()
13 | private val baseToRaw = IdentityHashMap()
14 |
15 | /**
16 | * Returns an Iterable to be traversed when matching issues. That means
17 | * that the traversal does not fail if method [.match]
18 | * is called.
19 | */
20 | val unmatchedRaws: Iterable
21 | get() {
22 | val result = mutableListOf()
23 | for (r in raws) {
24 | if (!rawToBase.containsKey(r)) {
25 | result.add(r)
26 | }
27 | }
28 | return result
29 | }
30 | val matchedRaws: Map
31 | get() = rawToBase
32 |
33 | /**
34 | * The base issues that are not matched by a raw issue and that need to be closed.
35 | */
36 | val unmatchedBases: Iterable
37 | get() {
38 | val result: MutableList = ArrayList()
39 | for (b in bases) {
40 | if (!baseToRaw.containsKey(b)) {
41 | result.add(b)
42 | }
43 | }
44 | return result
45 | }
46 |
47 | fun match(raw: R, base: B) {
48 | rawToBase[raw] = base
49 | baseToRaw[base] = raw
50 | }
51 |
52 | val isComplete: Boolean
53 | get() = rawToBase.size == raws.size
54 |
55 | }
--------------------------------------------------------------------------------
/src/main/kotlin/org/sonar/api/server/rule/RulesDefinition.kt:
--------------------------------------------------------------------------------
1 | package org.sonar.api.server.rule
2 |
3 | // Fake interface
4 | // The custom plugins currently extend CustomPlSqlRulesDefinition, which implements RulesDefinition from the SonarQube API
5 | interface RulesDefinition
6 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/sonar/plugins/plsqlopen/api/CustomPlSqlRulesDefinition.kt:
--------------------------------------------------------------------------------
1 | package org.sonar.plugins.plsqlopen.api
2 |
3 | // The custom plugins currently extend CustomPlSqlRulesDefinition from sonar-zpa-plugin and we don't
4 | // want to include the whole sonar-zpa-plugin as a dependency
5 | abstract class CustomPlSqlRulesDefinition : ZpaRulesDefinition
6 |
--------------------------------------------------------------------------------
/src/main/resources/logging.properties:
--------------------------------------------------------------------------------
1 | handlers=java.util.logging.ConsoleHandler
2 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
3 | java.util.logging.SimpleFormatter.format=%1$tH:%1$tM:%1$tS %4$s: %5$s %6$s%n
4 |
5 | .level=INFO
6 |
7 | # PF4J log
8 | org.pf4j.level=WARNING
9 |
--------------------------------------------------------------------------------
/template/changelog.tpl:
--------------------------------------------------------------------------------
1 | ## Highlights
2 |
3 | ## ZPA 3.6.0
4 |
5 | This release includes the [latest version of ZPA (3.6.0)](https://github.com/felipebz/zpa/releases/tag/3.6.0).
6 |
7 | Make sure to build your custom plugins with this version of ZPA before using them in ZPA CLI.
8 |
9 | ## Binaries
10 |
11 | ### 🌟 Universal
12 |
13 | This distribution requires an external Java runtime.
14 |
15 | * {{#f_release_download_url}}zpa-cli-{{projectVersion}}.zip{{/f_release_download_url}} (requires Java 17+)
16 |
17 | ### ☕️ Bundled Java Runtimes
18 |
19 | These binaries provide their own Java runtime.
20 |
21 | |Platform | Intel | Arm |
22 | | ------- | ----- | --- |
23 | | MacOS | {{#f_release_download_url}}zpa-cli-{{projectVersion}}-osx-x86_64.tar.gz{{/f_release_download_url}} | {{#f_release_download_url}}zpa-cli-{{projectVersion}}-osx-aarch_64.tar.gz{{/f_release_download_url}} |
24 | | Linux (glibc) | {{#f_release_download_url}}zpa-cli-{{projectVersion}}-linux-x86_64.tar.gz{{/f_release_download_url}} | {{#f_release_download_url}}zpa-cli-{{projectVersion}}-linux-aarch_64.tar.gz{{/f_release_download_url}} |
25 | | Alpine Linux (musl) | {{#f_release_download_url}}zpa-cli-{{projectVersion}}-linux_musl-x86_64.tar.gz{{/f_release_download_url}} | |
26 | | Windows | {{#f_release_download_url}}zpa-cli-{{projectVersion}}-windows-x86_64.zip{{/f_release_download_url}} | |
27 |
28 | ## Full changelog
29 |
30 | {{changelogChanges}}
31 |
--------------------------------------------------------------------------------
/zpa-config-example.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "schema.json",
3 | "rules": {
4 | "AddParenthesesInNestedExpression": "major",
5 | "CharacterDatatypeUsage": "minor",
6 | "CollapsibleIfStatements": "major",
7 | "ColumnsShouldHaveTableName": "major",
8 | "CommitRollback": "major",
9 | "ComparisonWithBoolean": "minor",
10 | "ComparisonWithNull": "blocker",
11 | "ConcatenationWithNull": "minor",
12 | "CursorBodyInPackageSpec": "major",
13 | "DbmsOutputPut": "minor",
14 | "DeadCode": "major",
15 | "DeclareSectionWithoutDeclarations": "info",
16 | "DisabledTest": "major",
17 | "DuplicateConditionIfElsif": "blocker",
18 | "DuplicatedValueInIn": "blocker",
19 | "EmptyBlock": "minor",
20 | "EmptyStringAssignment": "minor",
21 | "ExplicitInParameter": "minor",
22 | "FunctionWithOutParameter": "major",
23 | "IdenticalExpression": "blocker",
24 | "IfWithExit": "minor",
25 | "InequalityUsage": "major",
26 | "InsertWithoutColumns": "critical",
27 | "InvalidReferenceToObject": "major",
28 | "NotASelectedExpression": "critical",
29 | "NotFound": "minor",
30 | "NvlWithNullParameter": "blocker",
31 | "ParsingError": "info",
32 | "QueryWithoutExceptionHandling": {
33 | "level": "critical",
34 | "parameters": {
35 | "strict": "true"
36 | }
37 | },
38 | "RaiseStandardException": "major",
39 | "RedundantExpectation": "major",
40 | "ReturnOfBooleanExpression": "minor",
41 | "SameBranch": "major",
42 | "SameCondition": "blocker",
43 | "SelectAllColumns": "major",
44 | "SelectWithRownumAndOrderBy": "blocker",
45 | "ToCharInOrderBy": "major",
46 | "ToDateWithoutFormat": "major",
47 | "TooManyRowsHandler": "critical",
48 | "UnhandledUserDefinedException": "critical",
49 | "UnnecessaryAliasInQuery": {
50 | "level": "minor",
51 | "parameters": {
52 | "acceptedLength": "3"
53 | }
54 | },
55 | "UnnecessaryElse": "minor",
56 | "UnnecessaryLike": "minor",
57 | "UnnecessaryNullStatement": "minor",
58 | "UnusedCursor": "major",
59 | "UnusedParameter": {
60 | "level": "major",
61 | "parameters": {
62 | "ignoreMethods": ""
63 | }
64 | },
65 | "UnusedVariable": "major",
66 | "UselessParenthesis": "minor",
67 | "VariableHiding": "major",
68 | "VariableInCount": "blocker",
69 | "VariableInitializationWithFunctionCall": "major",
70 | "VariableInitializationWithNull": "minor",
71 | "VariableName": {
72 | "level": "minor",
73 | "parameters": {
74 | "regexp": "[a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?"
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------