├── .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 | [![Build](https://github.com/felipebz/zpa-cli/actions/workflows/build.yml/badge.svg?branch=main)](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 | --------------------------------------------------------------------------------