13 |
14 | # General overview creating your own Docker containers
15 |
16 | The `Dockerfile` provided in this repository will create a minimum,
17 | Java-based Docker environment which is able to run a IDEA CLI
18 | inspection.
19 |
20 | It is based on
21 |
22 | - IntelliJ Community Edition (vanilla)
23 |
24 | - AdoptOpenJDK Java 11
25 |
26 | In your practical usage scenario you might probably have different needs
27 | like:
28 |
29 | - Using the commercial Ultimate Edition for more support
30 |
31 | - using additional inspections provided by plugins
32 |
33 | - additional Tooling (like NodeJS)
34 |
35 | This guide tries to explain how to customize your idea-cli-inspector
36 | instance.
37 |
38 | - NOTE
39 | [@sylvainlaurent/docker-intellij-inspect](https://github.com/sylvainlaurent/docker-intellij-inspect)
40 | created a very similar approach which is in some aspects more
41 | advanced than this implementation. You might also have a look there.
42 |
43 | # Visually installing/adjusting IDEA settings
44 |
45 | IntelliJ stores its settings in a `IdeaIC2018.3/config` directory (or
46 | `.IntelliJ2018.3/config` for Ultimate editions). Most of these settings
47 | do not matter so they are excluded in the `.dockerignore` file.
48 |
49 | But if you want i.e. install additional plugins, set the licence server
50 | for the Ultimate Edition or add/define additional SDKs you can do this
51 | interactive.
52 |
53 | Therefore just run
54 |
55 | xhost si:localuser:root
56 | docker run -it --rm -e DISPLAY=$DISPLAY \
57 | -v /tmp/.X11-unix:/tmp/.X11-unix \
58 | -v `pwd`/home:/home \
59 | bentolor/idea-cli-inspector su ideainspect -c '/srv/idea.latest/bin/idea.sh'
60 |
61 | This will launch IDEA inside the docker container. After configuration
62 | check the modifications in `home/ideainspect` and add them to the
63 | Container (i.e. adjusting `.dockerignore`).
64 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Docker-based IntelliJ IDEA Inspections using idea-cli-inspector
2 | #
3 | # NOTE:
4 | # This Dockerfile provides quite a bunch of commented-out statements
5 | # as template for creatng your own, derived docker image in case i.e.
6 | # you want to use the Ultimate edition or need additional build tools.
7 | #
8 | FROM adoptopenjdk:11-jdk-hotspot
9 | MAINTAINER Benjamin Schmid
10 | LABEL maintainer="Benjamin Schmid "
11 |
12 | # First install some basic tools to get them or their latest versions (wget, apt).
13 | RUN apt-get update -q && apt-get install -q -y wget sudo locales zip unzip git \
14 | libxtst6 libxrender1 libxi6 && \
15 | apt-get autoremove --purge -y && apt-get clean && \
16 | rm /var/lib/apt/lists/*.* && rm -fr /tmp/* /var/tmp/*
17 |
18 | # The default locale is POSIX which breaks UTF-8 based javac files
19 | # NOTE:
20 | # This only taked effect for user root. Check home/ideainspect/.bashrc for main user
21 | # environment variables
22 | RUN locale-gen en_US.UTF-8 && update-locale en_US.UTF8
23 | ENV LANG "en_US.UTF-8"
24 | ENV LC_MESSAGES "C"
25 |
26 | # Provide a non-privileged user for running IntelliJ
27 | RUN useradd -mUs /bin/bash ideainspect
28 |
29 | # Install SDKMAN!
30 | RUN sudo -u ideainspect sh -c 'curl -s "https://get.sdkman.io" | bash'
31 |
32 | # Install Groovy Lang
33 | RUN sudo -u ideainspect bash -ci 'shopt -s expand_aliases ; sdk install groovy'
34 |
35 | # --------------- Install Oracle Java PPAs
36 | #RUN echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu xenial main" | tee /etc/apt/sources.list.d/webupd8team-java.list
37 | #RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
38 | #RUN echo "deb http://ppa.launchpad.net/linuxuprising/java/ubuntu xenial main" | tee /etc/apt/sources.list.d/linuxuprising-java.list
39 | #RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 73C3DB2A
40 | #
41 | # Mark Oracle license accepted
42 | #RUN echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections
43 | #RUN echo debconf shared/accepted-oracle-license-v1-1 seen true | debconf-set-selections
44 | #RUN echo oracle-java10-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections
45 | #
46 | # Install Java 8, Java 10, Groovy, mongodb-client & graphviz via package repository
47 | #RUN apt-get update && \
48 | # apt-get install -y --no-install-recommends \
49 | # oracle-java8-installer \
50 | # oracle-java8-set-default \
51 | # oracle-java8-unlimited-jce-policy \
52 | # oracle-java10-installer \
53 | # && \
54 | # apt-get autoremove --purge -y && \
55 | # apt-get clean && \
56 | # rm -fr /var/cache/oracle-jdk* && \
57 | # rm /var/lib/apt/lists/*.* && \
58 | # rm -fr /tmp/* /var/tmp/*
59 | #
60 | #
61 | # --------------- Install Android SDK Tools
62 | #RUN mkdir -p /srv/android-sdk && cd /srv/android-sdk && \
63 | # wget -nv https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip && \
64 | # echo "92ffee5a1d98d856634e8b71132e8a95d96c83a63fde1099be3d86df3106def9 sdk-tools-linux-4333796.zip" | sha256sum -c - && \
65 | # unzip -q sdk-tools-linux-4333796.zip && \
66 | # rm sdk-tools-linux-4333796.zip && \
67 | # find /srv/android-sdk -executable -type f -exec chmod o+x \{\} \;
68 | #
69 | # Install Android SDKs & Build Tools
70 | #RUN yes | /srv/android-sdk/tools/bin/sdkmanager --licenses > /dev/null && \
71 | # /srv/android-sdk/tools/bin/sdkmanager "platforms;android-26" "platforms;android-27" "build-tools;26.0.3" "build-tools;27.0.3" "platform-tools" > /dev/null
72 | #
73 | # --------------- Install 8.x node - this does an implicit apt-get update!
74 | #RUN ( curl -sL https://deb.nodesource.com/setup_8.x | bash - ) && \
75 | # apt-get install -y nodejs && \
76 | # apt-get clean && \
77 | # rm /var/lib/apt/lists/*.* && \
78 | # rm -fr /tmp/* /var/tmp/*
79 |
80 | #
81 | # Install IntelliJ IDEA
82 | #
83 | # IMPORTANT NOTES
84 | #
85 | # 1. V_IDEA_EDITION defines, which edition to build. Use C for Community or U for Ultimate
86 | #
87 | # 2. IDEA_CONFDIR is depending on the edition & version:
88 | # I.e. its .IdeaIC2018.3 for the 2018.3 Community edition and .IntelliJIdea2018.3 for the same ultimate dition
89 | #
90 | # 3. The first run to pre-populate the indexes won't work with ultimate edition, yet. This is due to outstanding features in
91 | # the current Docker daemon. See https://github.com/moby/buildkit/issues/763
92 | #
93 | ENV V_IDEA 2021.1.3
94 | ENV V_IDEA_EDITION C
95 | ENV IDEA_CONFDIR .IntelliJIdea2021.1
96 | # For Ultimate it is: ENV IDEA_CONFDIR .IntelliJIdea2019.2
97 | RUN cd /srv && \
98 | wget -nv https://download.jetbrains.com/idea/ideaI$V_IDEA_EDITION-$V_IDEA.tar.gz && \
99 | tar xf ideaI$V_IDEA_EDITION-$V_IDEA.tar.gz && \
100 | ln -s idea-I$V_IDEA_EDITION-* idea.latest && \
101 | # The idea-cli-inspector needs write access to the IDEA bin directory as a hack for scope
102 | chown -R ideainspect:ideainspect /srv/idea.latest/bin && \
103 | mkdir /home/ideainspect/$IDEA_CONFDIR && \
104 | ln -s /home/ideainspect/$IDEA_CONFDIR idea.config.latest && \
105 | rm ideaI$V_IDEA_EDITION-$V_IDEA.tar.gz
106 |
107 | # Point inspector to the new home
108 | # NOTE: This only takes effect for user `root`. For user ideainspect check home/ideainspect/.bashrc
109 | ENV IDEA_HOME /srv/idea.latest
110 |
111 | # Copy files into container
112 | COPY /idea-cli-inspector /
113 | COPY /docker-entrypoint.sh /
114 |
115 | # Bash Environments & Default IDEA config
116 | COPY /home /home
117 | RUN chown -R ideainspect:ideainspect /home/ideainspect
118 |
119 | # Prepare a sample project
120 | COPY / /project
121 | RUN chown -R ideainspect:ideainspect /project
122 |
123 | # Initial run to populate index i.e. for JDKs. This should reduce startup times.
124 | # NOTE: This won't run for Ultimate Edition, as a licence key is missing during execution and current docker
125 | # version provide no means to inject secrets during build time. JUST COMMENT IT OUT FOR NOW IN CASE OF ISSUES
126 | RUN [ "/docker-entrypoint.sh", "-r", "/project" ]
127 | #
128 | #
129 | # At some time this might work, by providing the idea.key as a secret during build time:
130 | #RUN --mount=type=secret,id=idea.key,target=/srv/idea.config.latest/idea.key,required,mode=0444 [ "/docker-entrypoint.sh", "-r","/project" ]
131 | #
132 | # To get this working you need to:
133 | # 1. add th following line on the very top of this file
134 | # # syntax = docker/dockerfile:experimental
135 | # 2. Build the image with BuildKit enabled:
136 | # DOCKER_BUILDKIT=1 docker build --secret id=idea.key,src=/home/ben/.IntelliJIdea2018.3/config/idea.key \
137 | # -t bentolor/idea-cli-inspector .
138 | #
139 |
140 | # Provide an entry point script which also creates starts Bamboo with a
141 | # dedicated user
142 | ENTRYPOINT ["/docker-entrypoint.sh"]
143 |
144 | # Define default command.
145 | #CMD ["--help"]
146 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A little command-line tool to integrate the awesome IntelliJ IDEA code
2 | inspections in your continuous integration (CI) process using Jenkins,
3 | Bamboo, et. al.
4 |
5 | # Quick start (using Docker and Maven)
6 |
7 | 1. Add a valid IDEA inspection profile file to your project (find them
8 | at `.idea/inspectionProfiles`)
9 |
10 | 2. Run within your project directory:
11 |
12 |
17 |
18 | Optionally you can also
19 |
20 | - Use/Add a complete IDEA configuration (`.idea`) to your project.
21 | This allows more fine control (i.e. defining inspection scopes)
22 |
23 | - add a `.ideainspect` to control i.e. ignored files/inspections and
24 | other details. See the example file provided with
25 | idea-cli-inspector.
26 |
27 | # Why this tool?
28 |
29 | IntelliJ IDEA offers a vast amount of very high-quality, built-in code
30 | inspections. Currently it has more than 1073 inspections to offer. Using
31 | project-shared inspection profiles it’s possible **to guide developers**
32 | in a wide range of coding aspects in a **non-annoying way**.
33 |
34 | In contrast to well-known quality tools like
35 | [SonarQube](http://www.sonarqube.org/) these inspections do have quite a
36 | few benefits:
37 |
38 | - **violations are instantly visible** during code writing with freely
39 | configurable severities and warning levels. (i.e. bold red errors,
40 | yellow warning or discreet hints as recommendation)
41 |
42 | - **false alarms can be easily and immediately suppressed.** A simple
43 | Alt-Enter directly at the location where they occur is enough.
44 | Alternatively you can adjust your inspection settings just
45 | on-the-fly (i.e. reduce level, disable inspection or configure
46 | inspection properties)
47 |
48 | - IDEA allows you to **check for new violations introduced with your
49 | changes** *while you are trying to commit them*
50 |
51 | - Many inspections in IDEA provide semi-automatic quickfixes and
52 | auto-corrections. So again sometimes addressing an issue is as
53 | simply as pressing Alt-Enter Right arrow Enter.
54 |
55 | - Because all inspections are based on IDEA´s Psi engine (which is a
56 | sort of permanent running syntax compiler) the inspections
57 | effectively work on an actual AST. So IDEA *understands* the code
58 | and does not only look for (textual) patterns which in return leads
59 | to a **significantly lower rate of false-positives.**
60 |
61 | Nevertheless, though IDEA offers on-the-fly analysis and error
62 | visualization it does not stop the developer in committing code
63 | violating these helpful guidelines into the project repository.
64 | JetBrain´s CI solution [TeamCity](https://www.jetbrains.com/teamcity/)
65 | does offer a easy and good integration. It is also possible to execute
66 | the IDEA project inspection [on the command
67 | line](https://www.jetbrains.com/idea/help/working-with-intellij-idea-features-from-command-line.html),
68 | which will produce a hard-readable set of XML files and no further
69 | support for integrating this into an automated tool chain.
70 |
71 | Therefore I did hack this little Groovy script to easily include &
72 | report inspection checks into your CI chain.
73 |
74 | # What it does
75 |
76 | This tool is aimed to simplify the inclusion of IDEA code inspection in
77 | your CI chain. It executes a command-line based run of an IntelliJ
78 | inspection run. This produces a set of hardly human-readable XML files
79 | in the target directory.
80 |
81 | Therefore the tool subsequently parses the generated inspection result
82 | files, allows to filter out selected result files and looks for messages
83 | with given severity (WARNING and ERROR by default).
84 |
85 | The tool will list issues in a humand-readable form and exit with return
86 | code 1 if it did find any issues (your CI tool should interpret this as
87 | failure) or will happily tell you that everyhing is fine.
88 |
89 | # Prerequisites & Limitations
90 |
91 | The script is developed in Groovy, so this has to be installed.
92 | Furthermore you need a valid installation of IntelliJ IDEA.
93 |
94 |
95 |
96 | Due to a limitation of IDEA itself it is not possible to run more than
97 | one IDEA instance at the same time. Therefore you must ensure, that no
98 | other IDEA is running on your PC / on your CI agents.
99 |
100 |
101 |
102 | # Usage
103 |
104 | ## Configuration file base usage
105 |
106 | IDEA CLI Inspector supports configuration via a `.ideainspect` file in
107 | the project root directory. Below is a example:
108 |
109 | # Levels to look for. Default: WARNING,ERROR
110 | #levels: WARNING,ERROR,INFO
111 |
112 | # Apply an "Custom scope" for the analysis run
113 | # This is _the prefered way_ to limit your inspection run to a part of your project files
114 | # as it takes effect within IDEA itself.
115 | # See: https://www.jetbrains.com/help/idea/2016.2/specify-inspection-scope-dialog.html
116 | #
117 | # HOWTO:
118 | # 1) Create a new scope excluding undesired folders/files (node_modules, doc, ...)
119 | # 2) Share the .idea/scopes/scopename.xml with the project
120 | # 3) Use the _name_ of the scope (not the file).
121 | # 4) Stick to a single word for best compability
122 | scope: inspector-code
123 |
124 | # Inspection result files to skip. For example "TodoComment" or "TodoComment.xml".
125 | #
126 | # NOTE: This does not have an effect on which inspections are effectively run by IDEA!
127 | # For the sake of performance better disable these inspections within your
128 | # inspection profile. This here is a last-resort mechanism if you want them
129 | # to appear in your IDE but not your CI process
130 | #skip: TodoComment,Annotator
131 | skip: GroovyAssignabilityCheck
132 |
133 | # Ignore issues affecting source files matching given regex. Example ".*/generated/.*".
134 | #
135 | # NOTE: This does not have an effect on the places IDEA looks. Therefore please prefer
136 | # declaring an "scope" and exclude those locations via the IDEA scoping mechanism
137 | # for the sake of performance.
138 | # This here is a last-resort mechanism if you have no other options to supress
139 | # specific places/warning.
140 | #skipfile: .*/generated/.*,src/main/Foo.java
141 |
142 | # Target directory to place the IDEA inspection XML result files. Default: target/inspection-results
143 | #resultdir: target/inspection-results
144 |
145 | # IDEA installation home directory. Default: IDEA_HOME environment variable or "idea".
146 | # ideahome: /home/ben/devel/idea
147 |
148 | # Limit IDEA inspection to this directory (This overrides scoping)
149 | # dir: .
150 |
151 | # Use this inspection profile file located ".idea/inspectionProfiles".
152 | profile: bentolor_2018.xml
153 |
154 | # IDEA project root directory containing the ".idea" directory
155 | # rootdir: .
156 |
157 | # Full path to the local idea.properties file. More info at:
158 | # http://tools.android.com/tech-docs/configuration
159 | # iprops: /Users/Shared/User/Library/Preferences/AndroidStudio2.1/idea.properties
160 |
161 | ## Command line based usage
162 |
163 | For a full / up-to-date list of options please run `idea-cli-inspector
164 | -h`:
165 |
166 | = IntellIJ IDEA Code Analysis Wrapper - v1.5.2 - @bentolor
167 |
168 | This tools runs IntelliJ IDEA inspection via command line and
169 | tries to parse the output.
170 |
171 | Example usage:
172 | ./idea-cli-inspector -i ~/devel/idea -r . -p myinspections.xml \
173 | -d src/main/java -s unused,Annotator,TodoComment.xml -l ERROR
174 |
175 | For more convenience you can pass all options in a `.ideainspect` file
176 | instead of passing it via command line
177 |
178 | usage: groovy idea-cli-inspector [options]
179 | -d,--dir Limit IDEA inspection to this directory.
180 | Overrides the scope argument.
181 | -h,--help Show usage information and quit
182 | -i,--ideahome IDEA or Android Studio installation home
183 | directory. Default: IDEA_HOME env var or `idea`
184 | -ip,--iprops Full path to your `idea.properties`. Only
185 | required if 1) you use --scope and 2) file is
186 | not located under in the default.
187 | Default: `/idea/bin/idea.properties`
188 | -l,--levels Levels to look for. Default: WARNING,ERROR
189 | -n,--dry-run Dry-run: Do not start IDEA, but run parsing
190 | -p,--profile Use this inspection profile file. If given an
191 | absolute path, the target file is used,
192 | otherwise the arg denotes a file located under
193 | `.idea/inspectionProfiles`.
194 | Default: `Project_Default.xml`
195 | -r,--rootdir IDEA project root directory containing the
196 | `.idea` directory. Default: Working directory
197 | -rf,--rootfile full path to the pom.xml or build.gradle file
198 | for the project. Useful if the project is maven
199 | or gradle based and its rootdir does not contain
200 | all the *.iml and .idea/modules.xml files
201 | -s,--skip Analysis result files to skip. For example
202 | `TodoComment` or `TodoComment.xml`.
203 | -sc,--scope The name of the "Custom scope" to apply. Custom
204 | scopes can be defined in the IDE. Share the
205 | resulting file in .idea/scopes/scopename.xml and
206 | provide the name of the scope (not file) here.
207 | -sf,--skipfile Ignore issues affecting source files matching
208 | given regex. Example: `.*/generated/.*`.
209 | -t,--resultdir Target directory to place the IDEA inspection
210 | XML result files.
211 | Default: `target/inspection-results`
212 | -v,--verbose Enable verbose logging
213 |
214 | # Example usage
215 |
216 | $ groovy idea-cli-inspector \
217 | -i ~/devel/idea \
218 | -r ~/projects/p1 \
219 | -p myinspections.xml \
220 | -d server \
221 | -s unused,Annotator,TodoComment.xml \
222 | -l ERROR
223 |
224 | This looks for a IntelliJ installation in `~/devel/idea`, tries to
225 | perform a CLI-based code inspection run on the IDEA project
226 | `~/projects/p1/.idea` with an inspection profile
227 | `~/projects/p1/.idea/inspectionProfiles/myinspections.xml` limiting the
228 | inspection run to the subdirectory `server` within your project.
229 |
230 | The IDEA inspection run will produce a set of `.xml` files. The amount,
231 | levels and result is based on the inspection profile you passed. Option
232 | `-s` tells to skip & ignore the warnings contained in the inspection
233 | result files `unused.xml`, `Annotator.xml` and `TodoComment.xml`. You
234 | can ommit the `.xml` suffix for convenience.
235 |
236 | By default it will then look for entries marked as `[WARNING]` or
237 | `[ERROR]` within the remaining inspection result report files. In our
238 | case we only care for ERROR entries. If it finds entries, it will report
239 | the file joined with a description pointing to the file location and the
240 | inspection rule.
241 |
242 | # Vanilla Maven and Gradle projects
243 |
244 | For many maven and gradle-based projects, .iml files and xml files under
245 | `.idea/libraries` are not committed to SCM as they are generated by
246 | IntelliJ based on maven/gradle files (see
247 |
248 | after "You may consider not to share the following").
249 |
250 | For such projects, the inspection must be launched by passing the path
251 | to the maven/gradle project file like in the following example:
252 |
253 | $ groovy idea-cli-inspector \
254 | --ideahome ~/devel/idea \
255 | --rootdir ~/projects/p1 \
256 | --rootfile ~/projects/p1/pom.xml
257 | --profile ~/myinspections.xml \
258 | -l ERROR
259 |
260 | # Example output
261 |
262 | ➜ idea-cli-inspector.git git:(master) ✗ ./idea-cli-inspector -i ~/devel/idea -r ../dashboard.git -p bens_idea15_2015_11.xml -d server -s Annotator,JSUnresolvedLibraryURL.xml,JavaDoc,TodoComment -l ERROR,WARNING
263 |
264 | = IntellIJ IDEA Code Analysis Wrapper - v1.0 - @bentolor
265 | #
266 | # Running IDEA IntelliJ Inspection
267 | #
268 | Executing: /home/ben/devel/idea/bin/idea.sh [/home/ben/devel/idea/bin/idea.sh, inspect, /home/ben/projects/idea-cli-inspector.git/../dashboard.git, /home/ben/projects/idea-cli-inspector.git/../dashboard.git/.idea/inspectionProfiles/bens_idea15_2015_11.xml, /home/ben/projects/idea-cli-inspector.git/../dashboard.git/target/inspection-results, -d, server]
269 | log4j:WARN No appenders could be found for logger (io.netty.util.internal.logging.InternalLoggerFactory).
270 | log4j:WARN Please initialize the log4j system properly.
271 | log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
272 | Please configure library 'Node.js v4.2.1 Core Modules' which is used in module 'client'
273 |
274 | ...
275 | IDEA spilling out quite a bunch of exceptions during inspection run
276 | ...
277 |
278 | #
279 | # Inspecting produced result files in ../dashboard.git/target/inspection-results
280 | #
281 | # Looking for: [[WARNING], [ERROR]]
282 | # Ignoring : [Annotator, JSUnresolvedLibraryURL, JavaDoc, TodoComment]
283 | --- ClassNamePrefixedWithPackageName.xml
284 | [WARNING] server/src/main/java/de/foo/dashboard/data/DatasetVerticle.java:28 -- Class name DatasetVerticle begins with its package name #loc
285 | [WARNING] server/src/main/java/de/foo/dashboard/data/DatasetBuilder.java:17 -- Class name DatasetBuilder begins with its package name #loc
286 |
287 | --- InterfaceNamingConvention.xml
288 | [WARNING] server/src/main/java/de/foo/dashboard/constants/Events.java:11 -- Interface name Events is too short (6 < 8) #loc
289 |
290 | --- SameParameterValue.xml
291 | [WARNING] server/src/main/java/de/foo/dashboard/data/DatasetBuilder.java:30 -- Actual value of parameter 'type' is always 'de.exxcellent.dashboard.constants.DatasetType.ARRAY'
292 |
293 | --- Skipping JavaDoc.xml
294 | --- Skipping TodoComment.xml
295 | --- DeprecatedClassUsageInspection.xml
296 | [WARNING] server/pom.xml:99 -- 'io.vertx.core.Starter' is deprecated
297 |
298 | --- Skipping JSUnresolvedLibraryURL.xml
299 | --- Skipping Annotator.xml
300 | --- unused.xml
301 | [WARNING] server/src/main/java/de/foo/dashboard/data/DatasetBuilder.java:40 -- Method is never used.
302 | [WARNING] server/src/main/java/de/foo/dashboard/constants/DatasetType.java:14 -- Field has no usages.
303 | [WARNING] server/src/main/java/de/foo/dashboard/constants/DatasetType.java:14 -- Field has no usages.
304 | [WARNING] server/src/main/java/de/foo/dashboard/data/DatasetVerticle.java:28 -- Class is not instantiated.
305 | [WARNING] server/src/main/java/de/foo/dashboard/transformers/History.java:23 -- Class is not instantiated.
306 |
307 | #
308 | # Analysis Result
309 | #
310 | Entries found. return code: 1
311 |
312 | # Running within a Docker container (i.e. Travis CI)
313 |
314 | Here is a `.travis.yml` which demonstrates how to run
315 | `idea-cli-inspector` within a Docker container. You can see this in
316 | practice running [here on Travis
317 | CI](https://www.travis-ci.org/bentolor/microframeworks-showcase/) with
318 | the source inspected [in my microframeworks-showcase
319 | project](https://github.com/bentolor/microframeworks-showcase/)
320 |
321 | Two things to note:
322 |
323 | - IDEA needs some very basic configuration already existing. At least
324 | i.e. the `.IntelliJIdea2018.1/config/options/jdk.table.xml` which
325 | defines the locations of the installed JDKs
326 |
327 | - The IDEA configuration directory location *varies from version to
328 | version and edition to edition*. I.e. it’s `~/.IntelliJIdea2018.1`
329 | for the IDEA 2018.1 Ultimate edition and `~/.IdeaIC2018.1` for the
330 | community edition
331 |
332 | - If you are using i.e. Node, Scala, VueJS etc. in your project please
333 | note, that these plugins bring more inspections to the table. If you
334 | want to have them in your CI/Docker run to, ensure that you add them
335 | to i.e. `.IntelliJIdea2017.3/config/plugins/` directory so they are
336 | picked up and effective.
337 |
338 | - You can build these required configurations either by manually
339 | adjusting and then copying those configuration file into the
340 | container or by i.e. manually starting IDEA within the container
341 | once with the configuration directories mapped as Docker volumes
342 | i.e. like:
343 |
344 |
345 |
346 | xhost si:localuser:root
347 | docker run -it --rm \
348 | --dns 192.168.144.18 --dns 8.8.8.8 \
349 | -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
350 | -v `pwd`/root/.IntelliJIdea2018.1:/root/.IntelliJIdea2018.1 \
351 | -v `pwd`/root/.java:/root/.java debug-ideacli-dockeragent \
352 | /bin/bash
353 |
354 | language: java
355 |
356 | before_cache:
357 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
358 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
359 |
360 | cache:
361 | directories:
362 | - $HOME/.gradle/caches/
363 | - $HOME/.gradle/wrapper/
364 |
365 | before_install:
366 | - sudo add-apt-repository ppa:mmk2410/intellij-idea -y
367 | - sudo apt-get update -q
368 | - sudo apt-get install intellij-idea-community -y
369 | - sudo apt-get install groovy -y
370 |
371 | install:
372 | - wget https://github.com/bentolor/idea-cli-inspector/archive/master.zip
373 | - unzip master.zip
374 | - sudo chmod -R aog+w /opt/intellij-idea-community/bin
375 |
376 | script:
377 | # Copy idea configuration template (mostly .IntelliJIdea2018.1/config/options/jdk.table.xml)
378 | - cp -r ./tools/idea-cli-inspector/root/.IntelliJIdea2018.1 /home/travis/
379 | # Duplicate for community config dir
380 | - cp -r ./tools/idea-cli-inspector/root/.IntelliJIdea2018.1 /home/travis/.IdeaIC2018.1
381 | - ./idea-cli-inspector-master/idea-cli-inspector -i /opt/intellij-idea-community
382 |
383 | # Troubleshooting & FAQ
384 |
385 | **My inspection runs very long and takes to much time. What can I do?.**
386 |
387 | First: Introduce and use a new scope where you exclude all folders
388 | and/or include only those folders which are relevant for your
389 | inspection. Typical folders which are not relevant are i.e.
390 | `node_modules`, `docs` or build output folders. Secondly think about
391 | creating and using a custom inspection profile for the purpose of the
392 | CI. There you i.e. might disable all inspections with INFO/HINT level or
393 | i.e. the spell checking.
394 |
395 | **I receive a error message *Please, specify sdk 'null' for module
396 | 'foo'*.**
397 |
398 | Probably you excluded `misc.xml` from the versionied IDEA project. Which
399 | is fine because this file is quite volatile. But this is the file where
400 | IDEA stores the "Root JDK".
401 |
402 | To fix this error simply assign every module a SDK other than "Project
403 | SDK".
404 |
405 | **The analysis seems to produce different results on subsequent runs on
406 | the same sources (esp. JavaScript).**
407 |
408 | This seems to be an issue with the IDEA caches which IntelliJ keeps i.e.
409 | under `.IntelliJ201X.X/system`. Try if deleting this directory prior to
410 | executing the analysis runs produces stable results.
411 |
412 | **What shall I pass as IDEA home directory for Mac OSX?.**
413 |
414 | `/Applications/IntelliJ\ IDEA.app` should be the default installation
415 | folder.
416 |
417 | **I’m using Android Studio and I can’t find `idea.properties`\!.**
418 |
419 | See the details about the location at
420 |
421 |
422 | **Scoping does not work?.**
423 |
424 | First: Did you versionate the `.idea/scope/scopename.xml` file in your
425 | project? Did you specify the Custom scope *name* and not the filename?
426 |
427 | If no, it might be that there is an issue that you are not pointing the
428 | `--iprops` option to the right `idea.properties` file or you i.e. don’t
429 | have write access for it.
430 |
431 | *Background information and troubleshooting:* Unfortunately IDEA yet
432 | does not offer a direct CLI option for applying scope. Therefore we need
433 | to pass a System property entries via modifying the `idea.properties`
434 | file. Check the output of the script if you spot any issues during this
435 | process. Check the content of `idea.properties` during the run and look
436 | out for you scope name.
437 |
438 | **I receive different results in my IDE vs. on the CI server.**
439 |
440 | Please check the following:
441 |
442 | - ❏ Same version of IDEA installed on CI vs. you installation?
443 |
444 | - ❏ Do you have the same plugins installed? Some bring own inspections
445 | or support fur understanding file formats (like `.vue` files with
446 | the vue plugin).
447 |
448 | - ❏ Do you delete the `.IntelliJ201X.X/system` folder prior to every
449 | run on CI (see above)?
450 |
451 | # Source code & Contributions
452 |
453 | The source code is located under
454 | .
455 |
456 | # License
457 |
458 | Licensed under the Apache License, Version 2.0 (the "License"); you may
459 | not use this file except in compliance with the License.
460 |
461 | You may obtain a copy of the License at
462 |
463 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 |
3 | repositories {
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | compile 'org.codehaus.groovy:groovy-all:2.4.15'
9 | }
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | if [[ "$1" =~ '-' || -z "$1" ]]; then
5 | CMD="cd /project && /idea-cli-inspector $@"
6 | echo "Calling $CMD"
7 | exec sudo -H -n -u ideainspect bash -l -c "$CMD"
8 | fi
9 |
10 | echo "Executing $@"
11 | exec "$@"
--------------------------------------------------------------------------------
/etc/travis-agent/.IntelliJIdea2018.1/config/options/jdk.table.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
--------------------------------------------------------------------------------
/home/ideainspect/.bashrc:
--------------------------------------------------------------------------------
1 | # Workaround: openjdk-Base image does not update links to /usr/lib/jvm/default-java/bin/java
2 | export JAVA_HOME=/opt/java/openjdk/
3 | export PATH=/opt/java/openjdk/bin:$PATH
4 |
5 | # Point to IDEA installation directory
6 | export ENV IDEA_HOME=/srv/idea.latest
7 |
8 | # Language settings from Dockerfile. Adopt also for user bamboo
9 | export LANG="en_US.UTF-8"
10 | export LC_MESSAGES="C"
11 |
12 | #THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
13 | export SDKMAN_DIR="/home/ideainspect/.sdkman"
14 | [[ -s "/home/ideainspect/.sdkman/bin/sdkman-init.sh" ]] && source "/home/ideainspect/.sdkman/bin/sdkman-init.sh"
--------------------------------------------------------------------------------
/home/ideainspect/.config/JetBrains/IdeaIC2021.1/options/filetypes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/home/ideainspect/.config/JetBrains/IdeaIC2021.1/options/ide.general.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/home/ideainspect/.config/JetBrains/IdeaIC2021.1/options/jdk.table.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
--------------------------------------------------------------------------------
/home/ideainspect/.config/JetBrains/IdeaIC2021.1/options/shared-indexes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/idea-cli-inspector:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env groovy
2 | import groovy.io.FileType
3 | import groovy.transform.Field
4 | import org.apache.commons.cli.Option
5 |
6 | import java.nio.file.Files
7 | import java.nio.file.Paths
8 |
9 | /*
10 | * Copyright 2015-2018 Benjamin Schmid, @bentolor
11 | *
12 | * Licensed under the Apache License, Version 2.0 (the "License");
13 | * you may not use this file except in compliance with the License.
14 | * You may obtain a copy of the License at
15 | *
16 | * http://www.apache.org/licenses/LICENSE-2.0
17 | *
18 | * Unless required by applicable law or agreed to in writing, software
19 | * distributed under the License is distributed on an "AS IS" BASIS,
20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 | * See the License for the specific language governing permissions and
22 | * limitations under the License.
23 | */
24 | /*
25 | https://github.com/bentolor/idea-cli-inspector
26 |
27 | Note to the reader:
28 | This is my very first Groovy script. Please be nice.
29 | */
30 | println "="
31 | println "= IntellIJ IDEA Code Analysis Wrapper - v1.8 - @bentolor"
32 | println "= "
33 | println "Use --help or see https://github.com/bentolor/idea-cli-inspector/ for help"
34 | println ""
35 |
36 | // Defaults
37 | def resultDir = "target/inspection-results"
38 | def acceptedLevels = ["[WARNING]", "[ERROR]"]
39 | def skipResults = []
40 | def skipIssueFilesRegex = []
41 | // Process timeout:
42 | // This is more or less broken, because after reaching the timeout value
43 | // it will kill only the Wrapper script and the IDEA process will happily
44 | // continue to run.
45 | // We should never reach this value and IDEA should always termiante on
46 | // its own
47 | def ideaWrapperTimeout = 1200 // Minutes
48 | //noinspection GroovyUnusedAssignment
49 | @Field Boolean verbose = false
50 |
51 | //
52 | // --- Command line option parsing
53 | //
54 | def configOpts = args.toList()
55 | configOpts.addAll(parseConfigFile())
56 | OptionAccessor cliOpts = parseCli(configOpts)
57 |
58 | // Levels
59 | if (cliOpts.l) {
60 | acceptedLevels.clear()
61 | cliOpts.ls.each { level -> acceptedLevels << "[" + level + "]" }
62 | }
63 | // Skip result XML files
64 | if (cliOpts.s) cliOpts.ss.each { skipFile -> skipResults << skipFile.replace(".xml", "") }
65 | // Skip issues affecting given file name regex
66 | if (cliOpts.sf) cliOpts.sfs.each { skipRegex -> skipIssueFilesRegex << skipRegex }
67 | // target directory
68 | if (cliOpts.t) resultDir = cliOpts.t
69 |
70 | // IDEA home
71 | File ideaPath = findIdeaExecutable(cliOpts)
72 | // Passed project root Directory or working directory
73 | def rootDir = cliOpts.r ? new File(cliOpts.r) : Paths.get(".").toAbsolutePath().normalize().toFile()
74 | //noinspection GroovyAssignabilityCheck
75 | def rootfilePath = cliOpts.rf ? new File(cliOpts.rf) : rootDir
76 |
77 | def dotIdeaDir = new File(rootDir, ".idea")
78 | if (!cliOpts.rf) {
79 | assertPathIsDir(dotIdeaDir, "IDEA project directory",
80 | "Please set the `rootdir` property to the location of your `.idea` project")
81 | }
82 |
83 | // Retrieve path to Inspection Profile file
84 | //noinspection GroovyAssignabilityCheck,GroovyAssignabilityCheck
85 | def profilePath = cliOpts.p ? new File(cliOpts.p) : null
86 | if( profilePath == null || !profilePath.absolute) {
87 | profileName = cliOpts.p ?: "Project_Default.xml"
88 | // if the given arg is not a full path, then firs try withing the root dir
89 | profilePath = new File(rootDir.path + File.separator + profileName)
90 |
91 | // is it there?
92 | if (!profilePath.exists() || !profilePath.file) {
93 | // If not try to find the inspection profile within the the .idea dir
94 | profilePath = new File(dotIdeaDir.path + File.separator + "inspectionProfiles" + File.separator + profileName)
95 | }
96 | }
97 | assertPathIsFile(profilePath, "IDEA inspection profile file")
98 |
99 | // Prepare result directory
100 | def resultPath = new File(resultDir)
101 | if (!resultPath.absolute) resultPath = new File(rootDir, resultDir)
102 | if (resultPath.exists() && !resultPath.deleteDir()) fail "Unable to remove result dir " + resultPath.absolutePath
103 | if (!resultPath.mkdirs()) fail "Unable to create result dir " + resultPath.absolutePath
104 |
105 | //
106 | // --- Actually running IDEA
107 | //
108 |
109 | // ~/projects/dashboard.git/. ~/projects/dashboard.git/.idea/inspectionProfiles/bens_idea15_2015_11.xml /tmp/ -d server
110 | def ideaArgs = [ideaPath.path, "inspect", rootfilePath.absolutePath, profilePath.absolutePath, resultPath.absolutePath]
111 | ideaArgs << ((cliOpts.v) ? "-v2" : "-v1")
112 | if (cliOpts.d) ideaArgs << "-d" << cliOpts.d
113 |
114 | // Did user define a Analysis "Scope"? We need a dirty workaround
115 | File origPropFile = applyScopeViaPropFile(cliOpts)
116 |
117 | if (cliOpts.pre) {
118 | if (!cliOpts.n) {
119 | def preTimeout = 1200
120 | println "Executing '${cliOpts.pre}' for maximum ${preTimeout}s"
121 | def sout = new StringBuilder(), serr = new StringBuilder()
122 | def preScript = cliOpts.pre.execute([], rootDir)
123 | preScript.consumeProcessOutput(sout, serr)
124 | preScript.waitForOrKill(preTimeout * 1000)
125 | preScript.waitForProcessOutput()
126 | print "$sout$serr"
127 |
128 | def exitValue = preScript.exitValue()
129 | if (exitValue != 0) fail("Pre-process script returned with an unexpected return code of $exitValue")
130 |
131 | } else {
132 | println "Dry-run: Would execute: '${cliOpts.pre}'"
133 | }
134 | }
135 |
136 |
137 | println "\n#"
138 | println "# Running IDEA IntelliJ Inspection"
139 | println "#"
140 | println "Executing: " + ideaArgs.join(" ")
141 | def exitValue = 0
142 | if (!cliOpts.n) {
143 | def processBuilder = new ProcessBuilder(ideaArgs)
144 | processBuilder.redirectErrorStream(true)
145 | processBuilder.directory(rootDir)
146 |
147 | println "~" * 80
148 | def ideaProcess = processBuilder.start()
149 | ideaProcess.consumeProcessOutput((OutputStream) System.out, System.err)
150 | ideaProcess.waitForOrKill(1000 * 60 * ideaWrapperTimeout)
151 | ideaProcess.waitForProcessOutput()
152 | exitValue = ideaProcess.exitValue()
153 | println "~" * 80
154 | } else {
155 | println("Dry-run: Not starting IDEA process")
156 | }
157 |
158 | // Scope workaround: Clean up time
159 | cleanupIdeaProps(cliOpts, origPropFile)
160 |
161 | if (exitValue != 0) fail("IDEA process returned with an unexpected return code of $exitValue")
162 |
163 | //
164 | // --- Now lets look on the results
165 | //
166 | def returnCode = analyzeResult(resultPath, acceptedLevels, skipResults, skipIssueFilesRegex)
167 | System.exit(returnCode)
168 |
169 |
170 | // ===============================================================================================
171 | // ==== End of script body
172 | // ===============================================================================================
173 |
174 |
175 | //
176 | // --- Helper functions
177 | //
178 | private List parseConfigFile() {
179 | // Parse root dir with minimal CliBuilder
180 | def cliBuilder = new CliBuilder()
181 | cliBuilder.with {
182 | r argName: 'dir', longOpt: 'rootdir', args: 1, required: false,
183 | 'IDEA project root directory containing the ".idea" directory'
184 | v argName: 'verbose', longOpt: 'verbose', args: 0, required: false,
185 | 'Enable verbose logging & debugging'
186 | }
187 |
188 | def opt = cliBuilder.parse(args)
189 | verbose = verbose ?: (opt && opt.v)
190 | def rootDir = opt != null && opt.r ? opt.r : '.'
191 |
192 | def configFile = new File(rootDir + '/.ideainspect')
193 | def configArgs = []
194 | if (configFile.exists()) {
195 | if (verbose) println "Parsing " + configFile.absolutePath
196 |
197 | //noinspection GroovyMissingReturnStatement
198 | configFile.eachLine { line ->
199 | def values = line.split(':', 2)
200 | if (!line.startsWith('#') && values.length == 2) {
201 | configArgs << ('--' + values[0].trim())
202 | configArgs << values[1].trim()
203 | }
204 | }
205 | }
206 |
207 | if (verbose) println "Config file content: " << configArgs
208 |
209 | return configArgs
210 | }
211 |
212 | private OptionAccessor parseCli(List configArgs) {
213 | def cliBuilder = new CliBuilder(usage: 'groovy idea-cli-inspector [options]',
214 | stopAtNonOption: false)
215 | cliBuilder.with {
216 | h argName: 'help', longOpt: 'help', 'Show usage information and quit'
217 | l argName: 'level', longOpt: 'levels', args: Option.UNLIMITED_VALUES, valueSeparator: ',',
218 | 'Levels to look for. Default: WARNING,ERROR'
219 | s argName: 'file', longOpt: 'skip', args: Option.UNLIMITED_VALUES, valueSeparator: ',',
220 | 'Analysis result files to skip. For example `TodoComment` or `TodoComment.xml`.'
221 | sf argName: 'regex', longOpt: 'skipfile', args: Option.UNLIMITED_VALUES, valueSeparator: ',',
222 | 'Ignore issues affecting source files matching given regex. Example: `.*/generated/.*`.'
223 | sc argName: 'string', longOpt: 'scope', args: 1,
224 | 'The name of the "Custom scope" to apply. Custom scopes can be defined in the IDE. '+
225 | 'Share the resulting file in .idea/scopes/scopename.xml and provide the name of the scope (not file) here.'
226 | t argName: 'dir', longOpt: 'resultdir', args: 1,
227 | 'Target directory to place the IDEA inspection XML result files. \nDefault: `target/inspection-results`'
228 | i argName: 'dir', longOpt: 'ideahome', args: 1,
229 | 'IDEA or Android Studio installation home directory. Default: IDEA_HOME env var or `idea`'
230 | d argName: 'dir', longOpt: 'dir', args: 1, 'Limit IDEA inspection to this directory. Overrides the scope argument.'
231 | ip argName: 'file', longOpt: 'iprops', args: 1, 'Full path to your `idea.properties`. Only required if 1) you use --scope and 2) ' +
232 | 'file is not located under in the default. \nDefault: `/idea/bin/idea.properties`'
233 | p argName: 'file', longOpt: 'profile', args: 1,
234 | 'Use this inspection profile file. If given an absolute path, the target file is used, otherwise the arg ' +
235 | 'denotes a file located under the projct directory or `.idea/inspectionProfiles` (IDEA default location). ' +
236 | '\nDefault: `Project_Default.xml`'
237 | r argName: 'dir', longOpt: 'rootdir', args: 1,
238 | 'IDEA project root directory containing the `.idea` directory. Default: Current working directory'
239 | rf argName: 'file', longOpt: 'rootfile', args: 1,
240 | 'full path to the pom.xml or build.gradle file for the project. Useful if the project is maven or gradle ' +
241 | 'based and its rootdir does not contain all the *.iml and .idea/modules.xml files'
242 | v argName: 'verbose', longOpt: 'verbose', args: 0,
243 | 'Enable verbose logging'
244 | n argName: 'dry-run', longOpt: 'dry-run', args: 0,
245 | 'Dry-run: Do not start IDEA, but run parsing'
246 | pre argName: 'Command', longOpt: 'preexec', args: Option.UNLIMITED_VALUES,
247 | 'Execute the passed command prior to starting IDEA.'
248 | //to argName: 'minutes', longOpt: 'timeout', args: 1,
249 | // 'Timeout in Minutes to wait for IDEA to complete the inspection. Default:'
250 | }
251 |
252 | def opt = cliBuilder.parse(configArgs)
253 |
254 | if (!opt) {
255 | System.exit(1)
256 | } // will print usage automatically
257 | if (opt.help) {
258 | println "\nThis tools runs IntelliJ IDEA inspection via command line and"
259 | println "tries to parse the output. \n"
260 | println "Example usage:"
261 | println " ./idea-cli-inspector -i ~/devel/idea -r . -p myinspections.xml \\"
262 | println " -d src/main/java -s unused,Annotator,TodoComment.xml -l ERROR\n"
263 | println "For more convenience you can pass all options in a `.ideainspect` file"
264 | println "instead of passing it via command line\n"
265 | cliBuilder.usage()
266 | System.exit(1)
267 | }
268 | if (verbose) {
269 | List optDebug = []
270 | //noinspection GroovyAssignabilityCheck
271 | for (Option ocliOpt : cliBuilder.options.options) {
272 | optDebug.add(ocliOpt.longOpt << ": " << opt.getProperty(ocliOpt.longOpt))
273 | }
274 | println "Effective configuration: " << optDebug.join(", ")
275 | }
276 |
277 | opt
278 | }
279 |
280 | private File findIdeaExecutable(OptionAccessor cliOpts) {
281 | def platform = System.properties['os.name'], scriptPath
282 | def ideaHome = getIdeaHome(cliOpts)
283 | def executable = "idea"
284 | if (ideaHome.toLowerCase().contains("android")) executable = "studio"
285 |
286 | switch (platform) {
287 | case ~/^Windows.*/:
288 | scriptPath = "bin" + File.separator + executable + ".bat"
289 | break
290 | case "Mac OS X":
291 | scriptPath = "Contents/MacOS/" + executable
292 | break
293 | default:
294 | scriptPath = "bin/" + executable + ".sh"
295 | break
296 | }
297 |
298 | def ideaExecutable = new File(ideaHome + File.separator + scriptPath)
299 | assertPathIsFile(ideaExecutable, "IDEA Installation directory",
300 | "Use a IDEA_HOME environment variable or the `ideahome` property in `.ideainspect` \n" +
301 | "or the `-i` command line option to point me to a valid IntelliJ installation")
302 | ideaExecutable
303 | }
304 |
305 | private static String getIdeaHome(OptionAccessor cliOpts) {
306 | cliOpts.i ?: (System.getenv("IDEA_HOME") ?: "idea")
307 | }
308 |
309 | /**
310 | * This method workarounds the lack of a CLI argument for the analysis scope by temporarily adding a
311 | * scope parameter in the {@code idea.properties} file.
312 | * @param propertiesPath Path to `idea.properties`
313 | * @param scopeName The scope to apply
314 | * @return The backup path of the original `idea.properties`
315 | */
316 | private File applyScopeViaPropFile(OptionAccessor cliOpts) {
317 |
318 | if (!cliOpts.sc) return null
319 | String scopeName = cliOpts.sc
320 |
321 | if (cliOpts.n) {
322 | println "\nDry-run: You defined a analysis scope. We now would temporarily modify `idea.properties`."
323 | return null
324 | }
325 | println "\nYou defined a analysis scope. We need to temporarily modify `idea.properties` to get this working."
326 |
327 | File ideaPropsFile = findIdeaProperties(cliOpts)
328 | def newPropsContent = new ArrayList()
329 | File propertiesBackupFile = null
330 |
331 | if (ideaPropsFile.exists()) {
332 | // If the file already exists we copy it
333 | propertiesBackupFile = new File(ideaPropsFile.absolutePath + ".idea-cli-inspect." + System.currentTimeMillis())
334 | Files.copy(ideaPropsFile.toPath(), propertiesBackupFile.toPath())
335 | List lines = ideaPropsFile.readLines()
336 | for (line in lines) {
337 | if (!line.contains("idea.analyze.scope")) newPropsContent.add(line)
338 | }
339 | }
340 |
341 | // If the file does not exist, it is instantiated when written to
342 | newPropsContent.add("idea.analyze.scope=" + scopeName)
343 | ideaPropsFile.write(newPropsContent.join('\n'))
344 |
345 | def backupPath = propertiesBackupFile?.path ?: ""
346 | println "Added scope `" + scopeName + "` to `" + ideaPropsFile.absolutePath + "` Backup: `" + backupPath + "`"
347 |
348 | propertiesBackupFile
349 | }
350 |
351 | /**
352 | * Revert to original IDEA configuration from backup.
353 | */
354 | private cleanupIdeaProps(OptionAccessor cliOpts, File backupFile) {
355 | if (!cliOpts.sc || backupFile == null) return
356 | File ideaPropsFile = findIdeaProperties(cliOpts)
357 | ideaPropsFile.delete()
358 | if (backupFile?.exists()) {
359 | println "Recovering `" + backupFile.absolutePath + "` back to `" + ideaPropsFile.absolutePath + "`"
360 | backupFile.renameTo(ideaPropsFile)
361 | } else {
362 | println "Deleted temporarily created `" + ideaPropsFile.absolutePath + "`"
363 | }
364 | }
365 |
366 | /** Tries to locate the `idea.properties` file either via convention or via parameters. */
367 | private File findIdeaProperties(OptionAccessor cliOpts) {
368 | String propertiesPath
369 | if (cliOpts.ip) {
370 | propertiesPath = cliOpts.ip
371 | } else {
372 | propertiesPath = getIdeaHome(cliOpts) + "/bin/idea.properties"
373 | }
374 | def propsFile = new File(propertiesPath)
375 | assertPathIsFile(propsFile, "idea.properties",
376 | "IDEA currently does currently not allow to pass the desired inspection scope as program parameter.\n" +
377 | "Currently the only way is to set a temporary property in the `idea.properties` configuration file\n" +
378 | "of your IntelliJ installation. We did not find that file. Therefore you need to pass the full path\n" +
379 | "to this file if you want to restrict the analysis to a specific scope."
380 | )
381 | propsFile
382 | }
383 |
384 | @SuppressWarnings("GroovyUntypedAccess")
385 | private analyzeResult(File resultPath, List acceptedLeves,
386 | List skipResults, List skipIssueFilesRegex) {
387 |
388 | printAnalysisHeader(resultPath, acceptedLeves, skipResults, skipIssueFilesRegex)
389 |
390 | def allGood = true
391 |
392 | resultPath.eachFile(FileType.FILES) { file ->
393 |
394 | String fileContents = workaroundUnclosedProblemXmlTags(file.getText('UTF-8'))
395 |
396 | def xmlDocument = new XmlParser().parseText(fileContents)
397 | def fileIssues = []
398 | def xmlFileName = file.name
399 |
400 | if (skipResults.contains(xmlFileName.replace(".xml", ""))) {
401 | println "--- Skipping $xmlFileName"
402 | return
403 | }
404 |
405 | xmlDocument.problem.each { problem ->
406 | String severity = problem.problem_class.@severity
407 | String affectedFile = problem.file.text()
408 | boolean fileShouldBeIgnored = false
409 | skipIssueFilesRegex.each { String regex -> fileShouldBeIgnored = (fileShouldBeIgnored || affectedFile.matches(regex)) }
410 | if (acceptedLeves.contains(severity) && !fileShouldBeIgnored) {
411 | String problemDesc = problem.description.text()
412 | String line = problem.line.text()
413 | fileIssues << "$severity $affectedFile:$line -- $problemDesc"
414 | }
415 | }
416 |
417 | if (!fileIssues.empty) {
418 | allGood = false
419 | System.err.println("--- $xmlFileName")
420 | System.err.println(fileIssues.join("\n"))
421 | System.err.println("")
422 | }
423 | }
424 |
425 | printAnalysisFooter(allGood)
426 | return allGood ? 0 : 1
427 | }
428 |
429 | private void printAnalysisHeader(File resultPath, List acceptedLeves, List skipResults, List skipIssueFilesRegex) {
430 | println "\n#"
431 | println "# Inspecting produced result files in $resultPath"
432 | println "#"
433 | println "# Looking for levels : $acceptedLeves"
434 | println "# Ignoring result files : $skipResults"
435 | println "# Ignoring source files : $skipIssueFilesRegex"
436 | }
437 |
438 | private void printAnalysisFooter(boolean allGood) {
439 | println "\n#"
440 | println "# Analysis Result"
441 | println "#"
442 |
443 | println allGood ? "Looks great - everything seems to be ok!"
444 | : "Entries found. Returncode: 1"
445 | }
446 |
447 | /**
448 | * Workaround for wrong XML formatting. IDEA clutters "" all-over.
449 | * See Bug report : https://youtrack.jetbrains.com/issue/IDEA-148855
450 | * @param fileContents XML file content string
451 | * @return XML file content with duplicate {@code } entries removed.
452 | */
453 | private static String workaroundUnclosedProblemXmlTags(String fileContents) {
454 | fileContents = fileContents.replace("", "")
455 | fileContents = fileContents.replace("file://\$PROJECT_DIR\$/", "")
456 | fileContents = "" + fileContents.replaceAll("<.?problems.*>", "") + ""
457 | fileContents
458 | }
459 |
460 | /**
461 | * Fail if the passed File does not exist or is not a directory
462 | * @param path The path to test for being a directory
463 | * @param description A human-readable description what dir we are looking for
464 | * @param hint an optional hint what to do now...
465 | */
466 | private void assertPathIsDir(File path, String description, String hint = null) {
467 | if (!path.exists() || !path.directory) {
468 | println "PROBLEM: " + description + " `" + path.path + "` not found or not a directory!"
469 | if (hint) println "\n" + hint
470 | println "\nAborting."
471 | System.exit(1)
472 | }
473 | }
474 | /**
475 | * Fail if the passed File does not exist or is not a file
476 | * @param path The path to test for being a file
477 | * @param description A human-readable description what file we are looking for
478 | * @param hint an optional hint what to do now...
479 | */
480 | private void assertPathIsFile(File path, String description, String hint = null) {
481 | if (!path.exists() || !path.file) {
482 | println "PROBLEM: " + description + " `" + path.path + "` not found or not a file!"
483 | if (hint) println "\n" + hint
484 | println "\nAborting."
485 | System.exit(1)
486 | }
487 | }
488 |
489 | private void fail(String message) {
490 | println "FATAL ERROR: " + message
491 | println "\nAborting."
492 | System.exit(1)
493 | }
494 |
--------------------------------------------------------------------------------