mirror of
https://github.com/dlang-community/D-Scanner.git
synced 2025-04-30 07:10:06 +03:00
Compare commits
36 commits
v0.16.0-be
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dc907e4a24 | ||
![]() |
3a87c65bac | ||
![]() |
796d212b05 | ||
![]() |
a8c4a588b2 | ||
![]() |
cc1a2c0178 | ||
![]() |
ff0a9bc2ee | ||
![]() |
565087aa76 | ||
![]() |
fe8f7bd8bc | ||
![]() |
22c9f980ae | ||
![]() |
17f3286fef | ||
![]() |
433d1eb73e | ||
![]() |
9076f7bab3 | ||
![]() |
01e90ec4d8 | ||
![]() |
8612841365 | ||
![]() |
42033dcc55 | ||
![]() |
1e8f1ec9e6 | ||
![]() |
69d824f4f7 | ||
![]() |
3bf3f25f9a | ||
![]() |
87f85c7db7 | ||
![]() |
159e9c9eec | ||
![]() |
b43c8f45cf | ||
![]() |
fc1699bb97 | ||
![]() |
6491d792f5 | ||
![]() |
a958f9ac7b | ||
![]() |
c8262f4220 | ||
![]() |
f22b2e587c | ||
![]() |
5d67707744 | ||
![]() |
7601fe65f9 | ||
![]() |
c1e051bfba | ||
![]() |
48db254fb0 | ||
![]() |
d275361153 | ||
![]() |
fed654441f | ||
![]() |
4c759b072c | ||
![]() |
cae7d595b8 | ||
![]() |
5d3296cc0b | ||
![]() |
d7e15903dd |
85 changed files with 1742 additions and 543 deletions
|
@ -19,3 +19,7 @@ dfmt_space_after_keywords = true
|
||||||
dfmt_selective_import_space = true
|
dfmt_selective_import_space = true
|
||||||
dfmt_compact_labeled_statements = true
|
dfmt_compact_labeled_statements = true
|
||||||
dfmt_template_constraint_style = conditional_newline_indent
|
dfmt_template_constraint_style = conditional_newline_indent
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tests/it/autofix_ide/source_autofix.d text eol=lf
|
23
.github/workflows/default.yml
vendored
23
.github/workflows/default.yml
vendored
|
@ -57,6 +57,11 @@ jobs:
|
||||||
dmd: gdc-12
|
dmd: gdc-12
|
||||||
host: macos-latest
|
host: macos-latest
|
||||||
|
|
||||||
|
# Restrict DMD to macOS latest
|
||||||
|
- compiler:
|
||||||
|
dmd: dmd
|
||||||
|
host: macos-latest
|
||||||
|
|
||||||
# Omit dub builds for GDC because dub rejects the old fronted revision
|
# Omit dub builds for GDC because dub rejects the old fronted revision
|
||||||
- compiler:
|
- compiler:
|
||||||
dmd: gdc-12
|
dmd: gdc-12
|
||||||
|
@ -65,12 +70,19 @@ jobs:
|
||||||
include:
|
include:
|
||||||
- { do_report: 1, build: { type: dub, version: 'current' }, host: 'ubuntu-22.04', compiler: { version: dmd-latest, dmd: dmd } }
|
- { do_report: 1, build: { type: dub, version: 'current' }, host: 'ubuntu-22.04', compiler: { version: dmd-latest, dmd: dmd } }
|
||||||
|
|
||||||
|
- compiler:
|
||||||
|
dmd: dmd
|
||||||
|
host: macos-13
|
||||||
|
build:
|
||||||
|
type: 'dub'
|
||||||
|
version: 'current'
|
||||||
|
|
||||||
runs-on: ${{ matrix.host }}
|
runs-on: ${{ matrix.host }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# Clone repo + submodules
|
# Clone repo + submodules
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
@ -123,7 +135,7 @@ jobs:
|
||||||
dub build
|
dub build
|
||||||
dub test
|
dub test
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: bin-${{matrix.build.type}}-${{matrix.build.version}}-${{ matrix.compiler.dmd }}-${{ matrix.host }}
|
name: bin-${{matrix.build.type}}-${{matrix.build.version}}-${{ matrix.compiler.dmd }}-${{ matrix.host }}
|
||||||
path: bin
|
path: bin
|
||||||
|
@ -146,9 +158,14 @@ jobs:
|
||||||
fi
|
fi
|
||||||
"./bin/dscanner$EXE" --styleCheck -f "$FORMAT" src
|
"./bin/dscanner$EXE" --styleCheck -f "$FORMAT" src
|
||||||
|
|
||||||
|
- name: Integration Tests
|
||||||
|
run: ./it.sh
|
||||||
|
working-directory: tests
|
||||||
|
shell: bash
|
||||||
|
|
||||||
# Parse phobos to check for failures / crashes / ...
|
# Parse phobos to check for failures / crashes / ...
|
||||||
- name: Checkout Phobos
|
- name: Checkout Phobos
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: dlang/phobos
|
repository: dlang/phobos
|
||||||
path: phobos
|
path: phobos
|
||||||
|
|
24
.travis.sh
24
.travis.sh
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [[ $BUILD == dub ]]; then
|
|
||||||
if [[ -n $LIBDPARSE_VERSION ]]; then
|
|
||||||
rdmd ./d-test-utils/test_with_package.d $LIBDPARSE_VERSION libdparse -- dub test
|
|
||||||
elif [[ -n $DSYMBOL_VERSION ]]; then
|
|
||||||
rdmd ./d-test-utils/test_with_package.d $DSYMBOL_VERSION dsymbol -- dub test
|
|
||||||
else
|
|
||||||
echo 'Cannot run test without LIBDPARSE_VERSION nor DSYMBOL_VERSION environment variable'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
elif [[ $DC == ldc2 ]]; then
|
|
||||||
git submodule update --init --recursive
|
|
||||||
make test DC=ldmd2
|
|
||||||
else
|
|
||||||
git submodule update --init --recursive
|
|
||||||
make test
|
|
||||||
make lint
|
|
||||||
git clone https://www.github.com/dlang/phobos.git --depth=1
|
|
||||||
# just check that it doesn't crash
|
|
||||||
cd phobos/std && ../../bin/dscanner -S || true
|
|
||||||
fi
|
|
110
.travis.yml
110
.travis.yml
|
@ -1,110 +0,0 @@
|
||||||
dist: xenial
|
|
||||||
sudo: false
|
|
||||||
language: d
|
|
||||||
d:
|
|
||||||
- dmd
|
|
||||||
- ldc-beta
|
|
||||||
- ldc
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
env:
|
|
||||||
- BUILD=
|
|
||||||
- BUILD=dub LIBDPARSE_VERSION=min
|
|
||||||
- BUILD=dub LIBDPARSE_VERSION=max
|
|
||||||
- BUILD=dub DSYMBOL_VERSION=min
|
|
||||||
- BUILD=dub DSYMBOL_VERSION=max
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- /^v\d+\.\d+\.\d+([+-]\S*)*$/
|
|
||||||
script: "./.travis.sh"
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: GitHub Release
|
|
||||||
#if: tag IS present
|
|
||||||
d: ldc-1.13.0
|
|
||||||
os: linux
|
|
||||||
script: echo "Deploying to GitHub releases ..." && ./release.sh
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: $GH_REPO_TOKEN
|
|
||||||
file_glob: true
|
|
||||||
file: bin/dscanner-*.tar.gz
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
repo: dlang-community/D-Scanner
|
|
||||||
tags: true
|
|
||||||
- stage: GitHub Release
|
|
||||||
#if: tag IS present
|
|
||||||
d: ldc-1.13.0
|
|
||||||
os: osx
|
|
||||||
script: echo "Deploying to GitHub releases ..." && ./release.sh
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: $GH_REPO_TOKEN
|
|
||||||
file_glob: true
|
|
||||||
file: bin/dscanner-*.tar.gz
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
repo: dlang-community/D-Scanner
|
|
||||||
tags: true
|
|
||||||
- stage: GitHub Release
|
|
||||||
#if: tag IS present
|
|
||||||
d: dmd
|
|
||||||
os: linux
|
|
||||||
language: generic
|
|
||||||
script: echo "Deploying to GitHub releases ..." && ./release-windows.sh
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- p7zip-full
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: $GH_REPO_TOKEN
|
|
||||||
file_glob: true
|
|
||||||
file: bin/dscanner-*.zip
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
repo: dlang-community/D-Scanner
|
|
||||||
tags: true
|
|
||||||
- stage: GitHub Release
|
|
||||||
#if: tag IS present
|
|
||||||
d: dmd
|
|
||||||
os: linux
|
|
||||||
language: generic
|
|
||||||
script: echo "Deploying to GitHub releases ..." && ARCH=64 ./release-windows.sh
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- p7zip-full
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: $GH_REPO_TOKEN
|
|
||||||
file_glob: true
|
|
||||||
file: bin/dscanner-*.zip
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
repo: dlang-community/D-Scanner
|
|
||||||
tags: true
|
|
||||||
- stage: dockerhub-stable
|
|
||||||
if: tag IS present
|
|
||||||
d: ldc
|
|
||||||
os: linux
|
|
||||||
script:
|
|
||||||
- echo "Deploying to DockerHub..." && ./release.sh
|
|
||||||
- LATEST_TAG="$(git describe --abbrev=0 --tags)"
|
|
||||||
- docker build -t "dlangcommunity/dscanner:${LATEST_TAG} ."
|
|
||||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; then docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" ; fi
|
|
||||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; then docker push "dlangcommunity/dscanner:${LATEST_TAG}" ; fi
|
|
||||||
- stage: dockerhub-latest
|
|
||||||
d: ldc
|
|
||||||
os: linux
|
|
||||||
script:
|
|
||||||
- echo "Deploying to DockerHub..." && ./release.sh
|
|
||||||
- docker build -t dlangcommunity/dscanner:latest .
|
|
||||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; then docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" ; fi
|
|
||||||
- if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]] ; then docker push dlangcommunity/dscanner:latest ; fi
|
|
||||||
stages:
|
|
||||||
- name: test
|
|
||||||
if: type = pull_request or (type = push and branch = master)
|
|
20
README.md
20
README.md
|
@ -91,6 +91,11 @@ dscanner -S source/
|
||||||
dscanner --report source/
|
dscanner --report source/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `--report` switch includes all information, plus cheap to compute autofixes
|
||||||
|
that are already resolved ahead of time, as well as the names for the autofixes
|
||||||
|
that need to be resolved using the `--resolveMessage` switch like described
|
||||||
|
below.
|
||||||
|
|
||||||
You can also specify custom formats using `-f` / `--errorFormat`, where there
|
You can also specify custom formats using `-f` / `--errorFormat`, where there
|
||||||
are also built-in formats for GitHub Actions:
|
are also built-in formats for GitHub Actions:
|
||||||
|
|
||||||
|
@ -101,7 +106,7 @@ dscanner -S -f github source/
|
||||||
dscanner -S -f '{filepath}({line}:{column})[{type}]: {message}' source/
|
dscanner -S -f '{filepath}({line}:{column})[{type}]: {message}' source/
|
||||||
```
|
```
|
||||||
|
|
||||||
To collect automatic issue fixes for a given location use
|
To resolve automatic issue fixes for a given location use
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# collecting automatic issue fixes
|
# collecting automatic issue fixes
|
||||||
|
@ -197,7 +202,7 @@ To avoid these cases, it's possible to pass the "--skipTests" option.
|
||||||
#### Configuration
|
#### Configuration
|
||||||
By default all checks are enabled. Individual checks can be enabled or disabled
|
By default all checks are enabled. Individual checks can be enabled or disabled
|
||||||
by using a configuration file. Such a file can be placed, for example, is the root directory of your project.
|
by using a configuration file. Such a file can be placed, for example, is the root directory of your project.
|
||||||
Running ```dscanner --defaultConfig``` will generate a default configuration file and print the file's location.
|
Running `dscanner --defaultConfig` will generate a default configuration file and print the file's location.
|
||||||
You can also specify the path to a configuration file by using the "--config" option if
|
You can also specify the path to a configuration file by using the "--config" option if
|
||||||
you want to override the default or the local settings.
|
you want to override the default or the local settings.
|
||||||
|
|
||||||
|
@ -301,8 +306,15 @@ and case tokens in the file.
|
||||||
|
|
||||||
### Syntax Highlighting
|
### Syntax Highlighting
|
||||||
The "--highlight" option prints the given source file as syntax-highlighted HTML
|
The "--highlight" option prints the given source file as syntax-highlighted HTML
|
||||||
to the standard output. The CSS styling is currently hard-coded to use the
|
to the standard output. The CSS styling uses the [Solarized](http://ethanschoonover.com/solarized)
|
||||||
[Solarized](http://ethanschoonover.com/solarized) color scheme.
|
color scheme by default, but can be customised using the "--theme" option.
|
||||||
|
|
||||||
|
The following themes are available:
|
||||||
|
|
||||||
|
- `solarized`
|
||||||
|
- `solarized-dark`
|
||||||
|
- `gruvbox`
|
||||||
|
- `gruvbox-dark`
|
||||||
|
|
||||||
No example. It would take up too much space
|
No example. It would take up too much space
|
||||||
|
|
||||||
|
|
2
dub.json
2
dub.json
|
@ -11,7 +11,7 @@
|
||||||
"built_with_dub"
|
"built_with_dub"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"libdparse": ">=0.23.1 <0.24.0",
|
"libdparse": ">=0.23.1 <0.26.0",
|
||||||
"dcd:dsymbol": ">=0.16.0-beta.2 <0.17.0",
|
"dcd:dsymbol": ">=0.16.0-beta.2 <0.17.0",
|
||||||
"inifiled": "~>1.3.1",
|
"inifiled": "~>1.3.1",
|
||||||
"emsi_containers": "~>0.9.0",
|
"emsi_containers": "~>0.9.0",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"emsi_containers": "0.9.0",
|
"emsi_containers": "0.9.0",
|
||||||
"inifiled": "1.3.3",
|
"inifiled": "1.3.3",
|
||||||
"libddoc": "0.8.0",
|
"libddoc": "0.8.0",
|
||||||
"libdparse": "0.23.2",
|
"libdparse": "0.25.0",
|
||||||
"stdx-allocator": "2.77.5"
|
"stdx-allocator": "2.77.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit fe6d1e38fb4fc04323170389cfec67ed7fd4e24a
|
Subproject commit f8a6c28589aae180532fb460a1b22e92a0978292
|
2
makefile
2
makefile
|
@ -86,8 +86,6 @@ else ifneq (,$(findstring gdc, $(DC)))
|
||||||
WRITE_TO_TARGET_NAME = -o $@
|
WRITE_TO_TARGET_NAME = -o $@
|
||||||
endif
|
endif
|
||||||
|
|
||||||
SHELL:=/usr/bin/env bash
|
|
||||||
|
|
||||||
GITHASH = bin/githash.txt
|
GITHASH = bin/githash.txt
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,9 @@ final class AliasSyntaxCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"alias_syntax_check";
|
mixin AnalyzerInfo!"alias_syntax_check";
|
||||||
|
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AliasDeclaration ad)
|
override void visit(const AliasDeclaration ad)
|
||||||
|
|
|
@ -30,9 +30,9 @@ final class AllManCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"allman_braces_check";
|
mixin AnalyzerInfo!"allman_braces_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Token)[] tokens, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
foreach (i; 1 .. tokens.length - 1)
|
foreach (i; 1 .. tokens.length - 1)
|
||||||
{
|
{
|
||||||
const curLine = tokens[i].line;
|
const curLine = tokens[i].line;
|
||||||
|
|
227
src/dscanner/analysis/always_curly.d
Normal file
227
src/dscanner/analysis/always_curly.d
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
||||||
|
// http://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
|
||||||
|
module dscanner.analysis.always_curly;
|
||||||
|
|
||||||
|
import dparse.lexer;
|
||||||
|
import dparse.ast;
|
||||||
|
import dscanner.analysis.base;
|
||||||
|
import dsymbol.scope_ : Scope;
|
||||||
|
|
||||||
|
import std.array : back, front;
|
||||||
|
import std.algorithm;
|
||||||
|
import std.range;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
final class AlwaysCurlyCheck : BaseAnalyzer
|
||||||
|
{
|
||||||
|
mixin AnalyzerInfo!"always_curly_check";
|
||||||
|
|
||||||
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
|
///
|
||||||
|
this(BaseAnalyzerArguments args)
|
||||||
|
{
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test(L, B)(L loc, B s, string stmtKind)
|
||||||
|
{
|
||||||
|
if (!is(s == BlockStatement))
|
||||||
|
{
|
||||||
|
if (!s.tokens.empty)
|
||||||
|
{
|
||||||
|
AutoFix af = AutoFix.insertionBefore(s.tokens.front, " { ")
|
||||||
|
.concat(AutoFix.insertionAfter(s.tokens.back, " } "));
|
||||||
|
af.name = "Wrap in braces";
|
||||||
|
|
||||||
|
addErrorMessage(loc, KEY, stmtKind ~ MESSAGE_POSTFIX, [af]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addErrorMessage(loc, KEY, stmtKind ~ MESSAGE_POSTFIX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const(IfStatement) stmt)
|
||||||
|
{
|
||||||
|
auto s = stmt.thenStatement.statement;
|
||||||
|
this.test(stmt.thenStatement, s, "if");
|
||||||
|
if (stmt.elseStatement !is null)
|
||||||
|
{
|
||||||
|
auto e = stmt.elseStatement.statement;
|
||||||
|
this.test(stmt.elseStatement, e, "else");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const(ForStatement) stmt)
|
||||||
|
{
|
||||||
|
auto s = stmt.declarationOrStatement;
|
||||||
|
if (s.statement !is null)
|
||||||
|
{
|
||||||
|
this.test(s, s, "for");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const(ForeachStatement) stmt)
|
||||||
|
{
|
||||||
|
auto s = stmt.declarationOrStatement;
|
||||||
|
if (s.statement !is null)
|
||||||
|
{
|
||||||
|
this.test(s, s, "foreach");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const(TryStatement) stmt)
|
||||||
|
{
|
||||||
|
auto s = stmt.declarationOrStatement;
|
||||||
|
if (s.statement !is null)
|
||||||
|
{
|
||||||
|
this.test(s, s, "try");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt.catches !is null)
|
||||||
|
{
|
||||||
|
foreach (const(Catch) ct; stmt.catches.catches)
|
||||||
|
{
|
||||||
|
this.test(ct, ct.declarationOrStatement, "catch");
|
||||||
|
}
|
||||||
|
if (stmt.catches.lastCatch !is null)
|
||||||
|
{
|
||||||
|
auto sncnd = stmt.catches.lastCatch.statementNoCaseNoDefault;
|
||||||
|
if (sncnd !is null)
|
||||||
|
{
|
||||||
|
this.test(stmt.catches.lastCatch, sncnd, "finally");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const(WhileStatement) stmt)
|
||||||
|
{
|
||||||
|
auto s = stmt.declarationOrStatement;
|
||||||
|
if (s.statement !is null)
|
||||||
|
{
|
||||||
|
this.test(s, s, "while");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const(DoStatement) stmt)
|
||||||
|
{
|
||||||
|
auto s = stmt.statementNoCaseNoDefault;
|
||||||
|
if (s !is null)
|
||||||
|
{
|
||||||
|
this.test(s, s, "do");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.style.always_curly";
|
||||||
|
enum string MESSAGE_POSTFIX = " must be follow by a BlockStatement aka. { }";
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||||
|
import dscanner.analysis.helpers : assertAnalyzerWarnings, assertAutoFix;
|
||||||
|
import std.stdio : stderr;
|
||||||
|
|
||||||
|
StaticAnalysisConfig sac = disabledConfig();
|
||||||
|
sac.always_curly_check = Check.enabled;
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void testIf()
|
||||||
|
{
|
||||||
|
if(true) return; // [warn]: if must be follow by a BlockStatement aka. { }
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void testIf()
|
||||||
|
{
|
||||||
|
if(true) return; /+
|
||||||
|
^^^^^^^ [warn]: if must be follow by a BlockStatement aka. { } +/
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void testIf()
|
||||||
|
{
|
||||||
|
for(int i = 0; i < 10; ++i) return; // [warn]: for must be follow by a BlockStatement aka. { }
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void testIf()
|
||||||
|
{
|
||||||
|
foreach(it; 0 .. 10) return; // [warn]: foreach must be follow by a BlockStatement aka. { }
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void testIf()
|
||||||
|
{
|
||||||
|
while(true) return; // [warn]: while must be follow by a BlockStatement aka. { }
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
void testIf()
|
||||||
|
{
|
||||||
|
do return; while(true); return; // [warn]: do must be follow by a BlockStatement aka. { }
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest {
|
||||||
|
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||||
|
import dscanner.analysis.helpers : assertAnalyzerWarnings, assertAutoFix;
|
||||||
|
import std.stdio : stderr;
|
||||||
|
|
||||||
|
StaticAnalysisConfig sac = disabledConfig();
|
||||||
|
sac.always_curly_check = Check.enabled;
|
||||||
|
|
||||||
|
assertAutoFix(q{
|
||||||
|
void test() {
|
||||||
|
if(true) return; // fix:0
|
||||||
|
}
|
||||||
|
}c, q{
|
||||||
|
void test() {
|
||||||
|
if(true) { return; } // fix:0
|
||||||
|
}
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
|
assertAutoFix(q{
|
||||||
|
void test() {
|
||||||
|
foreach(_; 0 .. 10 ) return; // fix:0
|
||||||
|
}
|
||||||
|
}c, q{
|
||||||
|
void test() {
|
||||||
|
foreach(_; 0 .. 10 ) { return; } // fix:0
|
||||||
|
}
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
|
assertAutoFix(q{
|
||||||
|
void test() {
|
||||||
|
for(int i = 0; i < 10; ++i) return; // fix:0
|
||||||
|
}
|
||||||
|
}c, q{
|
||||||
|
void test() {
|
||||||
|
for(int i = 0; i < 10; ++i) { return; } // fix:0
|
||||||
|
}
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
|
assertAutoFix(q{
|
||||||
|
void test() {
|
||||||
|
do return; while(true) // fix:0
|
||||||
|
}
|
||||||
|
}c, q{
|
||||||
|
void test() {
|
||||||
|
do { return; } while(true) // fix:0
|
||||||
|
}
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
|
|
||||||
|
stderr.writeln("Unittest for AlwaysCurly passed.");
|
||||||
|
}
|
|
@ -22,9 +22,9 @@ final class AsmStyleCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"asm_style_check";
|
mixin AnalyzerInfo!"asm_style_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AsmBrExp brExp)
|
override void visit(const AsmBrExp brExp)
|
||||||
|
@ -32,11 +32,13 @@ final class AsmStyleCheck : BaseAnalyzer
|
||||||
if (brExp.asmBrExp !is null && brExp.asmBrExp.asmUnaExp !is null
|
if (brExp.asmBrExp !is null && brExp.asmBrExp.asmUnaExp !is null
|
||||||
&& brExp.asmBrExp.asmUnaExp.asmPrimaryExp !is null)
|
&& brExp.asmBrExp.asmUnaExp.asmPrimaryExp !is null)
|
||||||
{
|
{
|
||||||
addErrorMessage(brExp, "dscanner.confusing.brexp",
|
addErrorMessage(brExp, KEY,
|
||||||
"This is confusing because it looks like an array index. Rewrite a[1] as [a + 1] to clarify.");
|
"This is confusing because it looks like an array index. Rewrite a[1] as [a + 1] to clarify.");
|
||||||
}
|
}
|
||||||
brExp.accept(this);
|
brExp.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum string KEY = "dscanner.confusing.brexp";
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -23,9 +23,9 @@ final class AssertWithoutMessageCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"assert_without_msg";
|
mixin AnalyzerInfo!"assert_without_msg";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AssertExpression expr)
|
override void visit(const AssertExpression expr)
|
||||||
|
|
|
@ -40,21 +40,22 @@ public:
|
||||||
mixin AnalyzerInfo!"auto_function_check";
|
mixin AnalyzerInfo!"auto_function_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
package static const(Token)[] findAutoReturnType(const(FunctionDeclaration) decl)
|
package static const(Token)[] findAutoReturnType(const(FunctionDeclaration) decl)
|
||||||
{
|
{
|
||||||
auto autoFunTokens = decl.storageClasses
|
const(Token)[] lastAtAttribute;
|
||||||
.map!(a => a.token.type == tok!"auto"
|
foreach (storageClass; decl.storageClasses)
|
||||||
? [a.token]
|
{
|
||||||
: a.atAttribute
|
if (storageClass.token.type == tok!"auto")
|
||||||
? a.atAttribute.tokens
|
return storageClass.tokens;
|
||||||
: null)
|
else if (storageClass.atAttribute)
|
||||||
.filter!(a => a.length > 0);
|
lastAtAttribute = storageClass.atAttribute.tokens;
|
||||||
return autoFunTokens.empty ? null : autoFunTokens.front;
|
}
|
||||||
|
return lastAtAttribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const(FunctionDeclaration) decl)
|
override void visit(const(FunctionDeclaration) decl)
|
||||||
|
@ -81,7 +82,8 @@ public:
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
addErrorMessage(autoTokens, KEY, MESSAGE,
|
addErrorMessage(autoTokens, KEY, MESSAGE,
|
||||||
[AutoFix.replacement(autoTokens[0], "void")]);
|
[AutoFix.replacement(autoTokens[0], "", "Replace `auto` with `void`")
|
||||||
|
.concat(AutoFix.insertionAt(decl.name.index, "void "))]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +197,9 @@ unittest
|
||||||
^^^^ [warn]: %s +/
|
^^^^ [warn]: %s +/
|
||||||
auto doStuff(){} /+
|
auto doStuff(){} /+
|
||||||
^^^^ [warn]: %s +/
|
^^^^ [warn]: %s +/
|
||||||
|
@Custom
|
||||||
|
auto doStuff(){} /+
|
||||||
|
^^^^ [warn]: %s +/
|
||||||
int doStuff(){auto doStuff(){}} /+
|
int doStuff(){auto doStuff(){}} /+
|
||||||
^^^^ [warn]: %s +/
|
^^^^ [warn]: %s +/
|
||||||
auto doStuff(){return 0;}
|
auto doStuff(){return 0;}
|
||||||
|
@ -203,6 +208,7 @@ unittest
|
||||||
AutoFunctionChecker.MESSAGE,
|
AutoFunctionChecker.MESSAGE,
|
||||||
AutoFunctionChecker.MESSAGE,
|
AutoFunctionChecker.MESSAGE,
|
||||||
AutoFunctionChecker.MESSAGE,
|
AutoFunctionChecker.MESSAGE,
|
||||||
|
AutoFunctionChecker.MESSAGE,
|
||||||
), sac);
|
), sac);
|
||||||
|
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarnings(q{
|
||||||
|
@ -272,13 +278,19 @@ unittest
|
||||||
|
|
||||||
|
|
||||||
assertAutoFix(q{
|
assertAutoFix(q{
|
||||||
|
auto ref doStuff(){} // fix
|
||||||
auto doStuff(){} // fix
|
auto doStuff(){} // fix
|
||||||
@property doStuff(){} // fix
|
@property doStuff(){} // fix
|
||||||
@safe doStuff(){} // fix
|
@safe doStuff(){} // fix
|
||||||
|
@Custom
|
||||||
|
auto doStuff(){} // fix
|
||||||
}c, q{
|
}c, q{
|
||||||
|
ref void doStuff(){} // fix
|
||||||
void doStuff(){} // fix
|
void doStuff(){} // fix
|
||||||
@property void doStuff(){} // fix
|
@property void doStuff(){} // fix
|
||||||
@safe void doStuff(){} // fix
|
@safe void doStuff(){} // fix
|
||||||
|
@Custom
|
||||||
|
void doStuff(){} // fix
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
stderr.writeln("Unittest for AutoFunctionChecker passed.");
|
stderr.writeln("Unittest for AutoFunctionChecker passed.");
|
||||||
|
|
|
@ -17,9 +17,9 @@ final class AutoRefAssignmentCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"auto_ref_assignment_check";
|
mixin AnalyzerInfo!"auto_ref_assignment_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module m)
|
override void visit(const Module m)
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
module dscanner.analysis.base;
|
module dscanner.analysis.base;
|
||||||
|
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
import dparse.lexer : IdType, str, Token;
|
import dparse.lexer : IdType, str, Token, tok;
|
||||||
|
import dscanner.analysis.nolint;
|
||||||
import dsymbol.scope_ : Scope;
|
import dsymbol.scope_ : Scope;
|
||||||
import std.array;
|
import std.array;
|
||||||
import std.container;
|
import std.container;
|
||||||
|
import std.meta : AliasSeq;
|
||||||
import std.string;
|
import std.string;
|
||||||
import std.sumtype;
|
import std.sumtype;
|
||||||
|
|
||||||
|
@ -369,14 +371,41 @@ mixin template AnalyzerInfo(string checkName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct BaseAnalyzerArguments
|
||||||
|
{
|
||||||
|
string fileName;
|
||||||
|
const(Token)[] tokens;
|
||||||
|
const Scope* sc;
|
||||||
|
bool skipTests = false;
|
||||||
|
|
||||||
|
BaseAnalyzerArguments setSkipTests(bool v)
|
||||||
|
{
|
||||||
|
auto ret = this;
|
||||||
|
ret.skipTests = v;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class BaseAnalyzer : ASTVisitor
|
abstract class BaseAnalyzer : ASTVisitor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
deprecated("Don't use this constructor, use the one taking BaseAnalyzerArguments")
|
||||||
this(string fileName, const Scope* sc, bool skipTests = false)
|
this(string fileName, const Scope* sc, bool skipTests = false)
|
||||||
{
|
{
|
||||||
this.sc = sc;
|
BaseAnalyzerArguments args = {
|
||||||
this.fileName = fileName;
|
fileName: fileName,
|
||||||
this.skipTests = skipTests;
|
sc: sc,
|
||||||
|
skipTests: skipTests
|
||||||
|
};
|
||||||
|
this(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
this(BaseAnalyzerArguments args)
|
||||||
|
{
|
||||||
|
this.sc = args.sc;
|
||||||
|
this.tokens = args.tokens;
|
||||||
|
this.fileName = args.fileName;
|
||||||
|
this.skipTests = args.skipTests;
|
||||||
_messages = new MessageSet;
|
_messages = new MessageSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,6 +433,35 @@ public:
|
||||||
unittest_.accept(this);
|
unittest_.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a module declaration.
|
||||||
|
*
|
||||||
|
* When overriden, make sure to keep this structure
|
||||||
|
*/
|
||||||
|
override void visit(const(Module) mod)
|
||||||
|
{
|
||||||
|
if (mod.moduleDeclaration !is null)
|
||||||
|
{
|
||||||
|
with (noLint.push(NoLintFactory.fromModuleDeclaration(mod.moduleDeclaration)))
|
||||||
|
mod.accept(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mod.accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a declaration.
|
||||||
|
*
|
||||||
|
* When overriden, make sure to disable and reenable error messages
|
||||||
|
*/
|
||||||
|
override void visit(const(Declaration) decl)
|
||||||
|
{
|
||||||
|
with (noLint.push(NoLintFactory.fromDeclaration(decl)))
|
||||||
|
decl.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
AutoFix.CodeReplacement[] resolveAutoFix(
|
AutoFix.CodeReplacement[] resolveAutoFix(
|
||||||
const Module mod,
|
const Module mod,
|
||||||
scope const(Token)[] tokens,
|
scope const(Token)[] tokens,
|
||||||
|
@ -422,6 +480,8 @@ protected:
|
||||||
|
|
||||||
bool inAggregate;
|
bool inAggregate;
|
||||||
bool skipTests;
|
bool skipTests;
|
||||||
|
const(Token)[] tokens;
|
||||||
|
NoLint noLint;
|
||||||
|
|
||||||
template visitTemplate(T)
|
template visitTemplate(T)
|
||||||
{
|
{
|
||||||
|
@ -436,42 +496,58 @@ protected:
|
||||||
deprecated("Use the overload taking start and end locations or a Node instead")
|
deprecated("Use the overload taking start and end locations or a Node instead")
|
||||||
void addErrorMessage(size_t line, size_t column, string key, string message)
|
void addErrorMessage(size_t line, size_t column, string key, string message)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
_messages.insert(Message(fileName, line, column, key, message, getName()));
|
_messages.insert(Message(fileName, line, column, key, message, getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(const BaseNode node, string key, string message, AutoFix[] autofixes = null)
|
void addErrorMessage(const BaseNode node, string key, string message, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
addErrorMessage(Message.Diagnostic.from(fileName, node, message), key, autofixes);
|
addErrorMessage(Message.Diagnostic.from(fileName, node, message), key, autofixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(const Token token, string key, string message, AutoFix[] autofixes = null)
|
void addErrorMessage(const Token token, string key, string message, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
addErrorMessage(Message.Diagnostic.from(fileName, token, message), key, autofixes);
|
addErrorMessage(Message.Diagnostic.from(fileName, token, message), key, autofixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(const Token[] tokens, string key, string message, AutoFix[] autofixes = null)
|
void addErrorMessage(const Token[] tokens, string key, string message, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
addErrorMessage(Message.Diagnostic.from(fileName, tokens, message), key, autofixes);
|
addErrorMessage(Message.Diagnostic.from(fileName, tokens, message), key, autofixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(size_t[2] index, size_t line, size_t[2] columns, string key, string message, AutoFix[] autofixes = null)
|
void addErrorMessage(size_t[2] index, size_t line, size_t[2] columns, string key, string message, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
addErrorMessage(index, [line, line], columns, key, message, autofixes);
|
addErrorMessage(index, [line, line], columns, key, message, autofixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(size_t[2] index, size_t[2] lines, size_t[2] columns, string key, string message, AutoFix[] autofixes = null)
|
void addErrorMessage(size_t[2] index, size_t[2] lines, size_t[2] columns, string key, string message, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
auto d = Message.Diagnostic.from(fileName, index, lines, columns, message);
|
auto d = Message.Diagnostic.from(fileName, index, lines, columns, message);
|
||||||
_messages.insert(Message(d, key, getName(), autofixes));
|
_messages.insert(Message(d, key, getName(), autofixes));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(Message.Diagnostic diagnostic, string key, AutoFix[] autofixes = null)
|
void addErrorMessage(Message.Diagnostic diagnostic, string key, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
_messages.insert(Message(diagnostic, key, getName(), autofixes));
|
_messages.insert(Message(diagnostic, key, getName(), autofixes));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addErrorMessage(Message.Diagnostic diagnostic, Message.Diagnostic[] supplemental, string key, AutoFix[] autofixes = null)
|
void addErrorMessage(Message.Diagnostic diagnostic, Message.Diagnostic[] supplemental, string key, AutoFix[] autofixes = null)
|
||||||
{
|
{
|
||||||
|
if (noLint.containsCheck(key))
|
||||||
|
return;
|
||||||
_messages.insert(Message(diagnostic, supplemental, key, getName(), autofixes));
|
_messages.insert(Message(diagnostic, supplemental, key, getName(), autofixes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,3 +574,326 @@ const(Token)[] findTokenForDisplay(const Token[] tokens, IdType type, const(Toke
|
||||||
return tokens[i .. i + 1];
|
return tokens[i .. i + 1];
|
||||||
return fallback is null ? tokens : fallback;
|
return fallback is null ? tokens : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class ScopedBaseAnalyzer : BaseAnalyzer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
this(BaseAnalyzerArguments args)
|
||||||
|
{
|
||||||
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template ScopedVisit(NodeType)
|
||||||
|
{
|
||||||
|
override void visit(const NodeType n)
|
||||||
|
{
|
||||||
|
pushScopeImpl();
|
||||||
|
scope (exit)
|
||||||
|
popScopeImpl();
|
||||||
|
n.accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
|
mixin ScopedVisit!BlockStatement;
|
||||||
|
mixin ScopedVisit!ForeachStatement;
|
||||||
|
mixin ScopedVisit!ForStatement;
|
||||||
|
mixin ScopedVisit!Module;
|
||||||
|
mixin ScopedVisit!StructBody;
|
||||||
|
mixin ScopedVisit!TemplateDeclaration;
|
||||||
|
mixin ScopedVisit!WithStatement;
|
||||||
|
mixin ScopedVisit!WhileStatement;
|
||||||
|
mixin ScopedVisit!DoStatement;
|
||||||
|
// mixin ScopedVisit!SpecifiedFunctionBody; // covered by BlockStatement
|
||||||
|
mixin ScopedVisit!ShortenedFunctionBody;
|
||||||
|
|
||||||
|
override void visit(const SwitchStatement switchStatement)
|
||||||
|
{
|
||||||
|
switchStack.length++;
|
||||||
|
scope (exit)
|
||||||
|
switchStack.length--;
|
||||||
|
switchStatement.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const IfStatement ifStatement)
|
||||||
|
{
|
||||||
|
pushScopeImpl();
|
||||||
|
if (ifStatement.condition)
|
||||||
|
ifStatement.condition.accept(this);
|
||||||
|
if (ifStatement.thenStatement)
|
||||||
|
ifStatement.thenStatement.accept(this);
|
||||||
|
popScopeImpl();
|
||||||
|
|
||||||
|
if (ifStatement.elseStatement)
|
||||||
|
{
|
||||||
|
pushScopeImpl();
|
||||||
|
ifStatement.elseStatement.accept(this);
|
||||||
|
popScopeImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static foreach (T; AliasSeq!(CaseStatement, DefaultStatement, CaseRangeStatement))
|
||||||
|
override void visit(const T stmt)
|
||||||
|
{
|
||||||
|
// case and default statements always open new scopes and close
|
||||||
|
// previous case scopes
|
||||||
|
bool close = switchStack.length && switchStack[$ - 1].inCase;
|
||||||
|
bool b = switchStack[$ - 1].inCase;
|
||||||
|
switchStack[$ - 1].inCase = true;
|
||||||
|
scope (exit)
|
||||||
|
switchStack[$ - 1].inCase = b;
|
||||||
|
if (close)
|
||||||
|
{
|
||||||
|
popScope();
|
||||||
|
pushScope();
|
||||||
|
stmt.accept(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushScope();
|
||||||
|
stmt.accept(this);
|
||||||
|
popScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Called on new scopes, which includes for example:
|
||||||
|
///
|
||||||
|
/// - `module m; /* here, entire file */`
|
||||||
|
/// - `{ /* here */ }`
|
||||||
|
/// - `if () { /* here */ } else { /* here */ }`
|
||||||
|
/// - `foreach (...) { /* here */ }`
|
||||||
|
/// - `case 1: /* here */ break;`
|
||||||
|
/// - `case 1: /* here, up to next case */ goto case; case 2: /* here 2 */ break;`
|
||||||
|
/// - `default: /* here */ break;`
|
||||||
|
/// - `struct S { /* here */ }`
|
||||||
|
///
|
||||||
|
/// But doesn't include:
|
||||||
|
///
|
||||||
|
/// - `static if (x) { /* not a separate scope */ }` (use `mixin ScopedVisit!ConditionalDeclaration;`)
|
||||||
|
///
|
||||||
|
/// You can `mixin ScopedVisit!NodeType` to automatically call push/popScope
|
||||||
|
/// on occurences of that NodeType.
|
||||||
|
abstract void pushScope();
|
||||||
|
/// ditto
|
||||||
|
abstract void popScope();
|
||||||
|
|
||||||
|
void pushScopeImpl()
|
||||||
|
{
|
||||||
|
if (switchStack.length)
|
||||||
|
switchStack[$ - 1].scopeDepth++;
|
||||||
|
pushScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
void popScopeImpl()
|
||||||
|
{
|
||||||
|
if (switchStack.length)
|
||||||
|
switchStack[$ - 1].scopeDepth--;
|
||||||
|
popScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwitchStack
|
||||||
|
{
|
||||||
|
int scopeDepth;
|
||||||
|
bool inCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwitchStack[] switchStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
import core.exception : AssertError;
|
||||||
|
import dparse.lexer : getTokensForParser, LexerConfig, StringCache;
|
||||||
|
import dparse.parser : parseModule;
|
||||||
|
import dparse.rollback_allocator : RollbackAllocator;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.exception : assertThrown;
|
||||||
|
|
||||||
|
// test where we can:
|
||||||
|
// call `depth(1);` to check that the scope depth is at 1
|
||||||
|
// if calls are syntactically not valid, define `auto depth = 1;`
|
||||||
|
//
|
||||||
|
// call `isNewScope();` to check that the scope hasn't been checked with isNewScope before
|
||||||
|
// if calls are syntactically not valid, define `auto isNewScope = void;`
|
||||||
|
//
|
||||||
|
// call `isOldScope();` to check that the scope has already been checked with isNewScope
|
||||||
|
// if calls are syntactically not valid, define `auto isOldScope = void;`
|
||||||
|
|
||||||
|
class TestScopedAnalyzer : ScopedBaseAnalyzer
|
||||||
|
{
|
||||||
|
this(size_t codeLine)
|
||||||
|
{
|
||||||
|
super(BaseAnalyzerArguments("stdin"));
|
||||||
|
|
||||||
|
this.codeLine = codeLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const FunctionCallExpression f)
|
||||||
|
{
|
||||||
|
int depth = cast(int) stack.length;
|
||||||
|
if (f.unaryExpression && f.unaryExpression.primaryExpression
|
||||||
|
&& f.unaryExpression.primaryExpression.identifierOrTemplateInstance)
|
||||||
|
{
|
||||||
|
auto fname = f.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier.text;
|
||||||
|
if (fname == "depth")
|
||||||
|
{
|
||||||
|
assert(f.arguments.tokens.length == 3);
|
||||||
|
auto expected = f.arguments.tokens[1].text.to!int;
|
||||||
|
assert(expected == depth, "Expected depth="
|
||||||
|
~ expected.to!string ~ " in line " ~ (codeLine + f.tokens[0].line).to!string
|
||||||
|
~ ", but got depth=" ~ depth.to!string);
|
||||||
|
}
|
||||||
|
else if (fname == "isNewScope")
|
||||||
|
{
|
||||||
|
assert(!stack[$ - 1]);
|
||||||
|
stack[$ - 1] = true;
|
||||||
|
}
|
||||||
|
else if (fname == "isOldScope")
|
||||||
|
{
|
||||||
|
assert(stack[$ - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(const AutoDeclarationPart p)
|
||||||
|
{
|
||||||
|
int depth = cast(int) stack.length;
|
||||||
|
|
||||||
|
if (p.identifier.text == "depth")
|
||||||
|
{
|
||||||
|
assert(p.initializer.tokens.length == 1);
|
||||||
|
auto expected = p.initializer.tokens[0].text.to!int;
|
||||||
|
assert(expected == depth, "Expected depth="
|
||||||
|
~ expected.to!string ~ " in line " ~ (codeLine + p.tokens[0].line).to!string
|
||||||
|
~ ", but got depth=" ~ depth.to!string);
|
||||||
|
}
|
||||||
|
else if (p.identifier.text == "isNewScope")
|
||||||
|
{
|
||||||
|
assert(!stack[$ - 1]);
|
||||||
|
stack[$ - 1] = true;
|
||||||
|
}
|
||||||
|
else if (p.identifier.text == "isOldScope")
|
||||||
|
{
|
||||||
|
assert(stack[$ - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override void pushScope()
|
||||||
|
{
|
||||||
|
stack.length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
override void popScope()
|
||||||
|
{
|
||||||
|
stack.length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
alias visit = ScopedBaseAnalyzer.visit;
|
||||||
|
|
||||||
|
bool[] stack;
|
||||||
|
size_t codeLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testScopes(string code, size_t codeLine = __LINE__ - 1)
|
||||||
|
{
|
||||||
|
StringCache cache = StringCache(4096);
|
||||||
|
LexerConfig config;
|
||||||
|
RollbackAllocator rba;
|
||||||
|
auto tokens = getTokensForParser(code, config, &cache);
|
||||||
|
Module m = parseModule(tokens, "stdin", &rba);
|
||||||
|
|
||||||
|
auto analyzer = new TestScopedAnalyzer(codeLine);
|
||||||
|
analyzer.visit(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
testScopes(q{
|
||||||
|
auto isNewScope = void;
|
||||||
|
auto depth = 1;
|
||||||
|
auto isOldScope = void;
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThrown!AssertError(testScopes(q{
|
||||||
|
auto isNewScope = void;
|
||||||
|
auto isNewScope = void;
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertThrown!AssertError(testScopes(q{
|
||||||
|
auto isOldScope = void;
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertThrown!AssertError(testScopes(q{
|
||||||
|
auto depth = 2;
|
||||||
|
}));
|
||||||
|
|
||||||
|
testScopes(q{
|
||||||
|
auto isNewScope = void;
|
||||||
|
auto depth = 1;
|
||||||
|
|
||||||
|
void foo() {
|
||||||
|
isNewScope();
|
||||||
|
isOldScope();
|
||||||
|
depth(2);
|
||||||
|
switch (a)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
depth(4);
|
||||||
|
isOldScope();
|
||||||
|
case 2:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
if (a)
|
||||||
|
{
|
||||||
|
isNewScope();
|
||||||
|
depth(6);
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(6); // since cases/default opens new scope
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
isNewScope();
|
||||||
|
depth(6); // since cases/default opens new scope
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(6); // since cases/default opens new scope
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
depth(4);
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
depth(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
isOldScope();
|
||||||
|
depth(2);
|
||||||
|
|
||||||
|
switch (a)
|
||||||
|
{
|
||||||
|
isNewScope();
|
||||||
|
depth(3);
|
||||||
|
isOldScope();
|
||||||
|
default:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
isOldScope();
|
||||||
|
case 1:
|
||||||
|
isNewScope();
|
||||||
|
depth(4);
|
||||||
|
break;
|
||||||
|
isOldScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto isOldScope = void;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@ final class BodyOnDisabledFuncsCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"body_on_disabled_func_check";
|
mixin AnalyzerInfo!"body_on_disabled_func_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
static foreach (AggregateType; AliasSeq!(InterfaceDeclaration, ClassDeclaration,
|
static foreach (AggregateType; AliasSeq!(InterfaceDeclaration, ClassDeclaration,
|
||||||
|
|
|
@ -33,9 +33,9 @@ final class BuiltinPropertyNameCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"builtin_property_names_check";
|
mixin AnalyzerInfo!"builtin_property_names_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const FunctionDeclaration fd)
|
override void visit(const FunctionDeclaration fd)
|
||||||
|
|
|
@ -19,9 +19,9 @@ final class CommaExpressionCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"comma_expression_check";
|
mixin AnalyzerInfo!"comma_expression_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Expression ex)
|
override void visit(const Expression ex)
|
||||||
|
|
|
@ -165,7 +165,10 @@ struct StaticAnalysisConfig
|
||||||
string lambda_return_check = Check.enabled;
|
string lambda_return_check = Check.enabled;
|
||||||
|
|
||||||
@INI("Check for auto function without return statement")
|
@INI("Check for auto function without return statement")
|
||||||
string auto_function_check = Check.enabled;
|
string auto_function_check = Check.disabled;
|
||||||
|
|
||||||
|
@INI("Check that if|else|for|foreach|while|do|try|catch are always followed by a BlockStatement { }")
|
||||||
|
string always_curly_check = Check.disabled;
|
||||||
|
|
||||||
@INI("Check for sortedness of imports")
|
@INI("Check for sortedness of imports")
|
||||||
string imports_sortedness = Check.disabled;
|
string imports_sortedness = Check.disabled;
|
||||||
|
|
|
@ -14,9 +14,9 @@ final class ConstructorCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"constructor_check";
|
mixin AnalyzerInfo!"constructor_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const ClassDeclaration classDeclaration)
|
override void visit(const ClassDeclaration classDeclaration)
|
||||||
|
|
|
@ -53,10 +53,9 @@ final class CyclomaticComplexityCheck : BaseAnalyzer
|
||||||
int maxCyclomaticComplexity;
|
int maxCyclomaticComplexity;
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false,
|
this(BaseAnalyzerArguments args, int maxCyclomaticComplexity = 50)
|
||||||
int maxCyclomaticComplexity = 50)
|
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
this.maxCyclomaticComplexity = maxCyclomaticComplexity;
|
this.maxCyclomaticComplexity = maxCyclomaticComplexity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,19 +20,21 @@ final class DeleteCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"delete_check";
|
mixin AnalyzerInfo!"delete_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const DeleteExpression d)
|
override void visit(const DeleteExpression d)
|
||||||
{
|
{
|
||||||
addErrorMessage(d.tokens[0], "dscanner.deprecated.delete_keyword",
|
addErrorMessage(d.tokens[0], KEY,
|
||||||
"Avoid using the 'delete' keyword.",
|
"Avoid using the 'delete' keyword.",
|
||||||
[AutoFix.replacement(d.tokens[0], `destroy(`, "Replace delete with destroy()")
|
[AutoFix.replacement(d.tokens[0], `destroy(`, "Replace delete with destroy()")
|
||||||
.concat(AutoFix.insertionAfter(d.tokens[$ - 1], ")"))]);
|
.concat(AutoFix.insertionAfter(d.tokens[$ - 1], ")"))]);
|
||||||
d.accept(this);
|
d.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum string KEY = "dscanner.deprecated.delete_keyword";
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -23,9 +23,9 @@ final class DuplicateAttributeCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"duplicate_attribute";
|
mixin AnalyzerInfo!"duplicate_attribute";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Declaration node)
|
override void visit(const Declaration node)
|
||||||
|
@ -93,7 +93,7 @@ final class DuplicateAttributeCheck : BaseAnalyzer
|
||||||
if (hasAttribute)
|
if (hasAttribute)
|
||||||
{
|
{
|
||||||
string message = "Attribute '%s' is duplicated.".format(attributeName);
|
string message = "Attribute '%s' is duplicated.".format(attributeName);
|
||||||
addErrorMessage(tokens, "dscanner.unnecessary.duplicate_attribute", message,
|
addErrorMessage(tokens, KEY, message,
|
||||||
[AutoFix.replacement(tokens, "", "Remove second attribute " ~ attributeName)]);
|
[AutoFix.replacement(tokens, "", "Remove second attribute " ~ attributeName)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +149,8 @@ final class DuplicateAttributeCheck : BaseAnalyzer
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum string KEY = "dscanner.unnecessary.duplicate_attribute";
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -21,9 +21,9 @@ final class EnumArrayLiteralCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"enum_array_literal_check";
|
mixin AnalyzerInfo!"enum_array_literal_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool looking;
|
bool looking;
|
||||||
|
@ -47,7 +47,7 @@ final class EnumArrayLiteralCheck : BaseAnalyzer
|
||||||
if (part.initializer.nonVoidInitializer.arrayInitializer is null)
|
if (part.initializer.nonVoidInitializer.arrayInitializer is null)
|
||||||
continue;
|
continue;
|
||||||
addErrorMessage(part.initializer.nonVoidInitializer,
|
addErrorMessage(part.initializer.nonVoidInitializer,
|
||||||
"dscanner.performance.enum_array_literal",
|
KEY,
|
||||||
"This enum may lead to unnecessary allocation at run-time."
|
"This enum may lead to unnecessary allocation at run-time."
|
||||||
~ " Use 'static immutable "
|
~ " Use 'static immutable "
|
||||||
~ part.identifier.text ~ " = [ ...' instead.",
|
~ part.identifier.text ~ " = [ ...' instead.",
|
||||||
|
@ -58,6 +58,8 @@ final class EnumArrayLiteralCheck : BaseAnalyzer
|
||||||
}
|
}
|
||||||
autoDec.accept(this);
|
autoDec.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum string KEY = "dscanner.performance.enum_array_literal";
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -20,9 +20,9 @@ final class ExplicitlyAnnotatedUnittestCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"explicitly_annotated_unittests";
|
mixin AnalyzerInfo!"explicitly_annotated_unittests";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Declaration decl)
|
override void visit(const Declaration decl)
|
||||||
|
|
|
@ -74,9 +74,9 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const(StructDeclaration) sd)
|
override void visit(const(StructDeclaration) sd)
|
||||||
|
|
|
@ -22,9 +22,9 @@ final class FloatOperatorCheck : BaseAnalyzer
|
||||||
enum string KEY = "dscanner.deprecated.floating_point_operators";
|
enum string KEY = "dscanner.deprecated.floating_point_operators";
|
||||||
mixin AnalyzerInfo!"float_operator_check";
|
mixin AnalyzerInfo!"float_operator_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const RelExpression r)
|
override void visit(const RelExpression r)
|
||||||
|
|
|
@ -28,9 +28,9 @@ final class FunctionAttributeCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"function_attribute_check";
|
mixin AnalyzerInfo!"function_attribute_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const InterfaceDeclaration dec)
|
override void visit(const InterfaceDeclaration dec)
|
||||||
|
|
|
@ -22,9 +22,9 @@ final class HasPublicExampleCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"has_public_example";
|
mixin AnalyzerInfo!"has_public_example";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module mod)
|
override void visit(const Module mod)
|
||||||
|
@ -88,6 +88,8 @@ final class HasPublicExampleCheck : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.style.has_public_example";
|
||||||
|
|
||||||
bool hasDitto(Decl)(const Decl decl)
|
bool hasDitto(Decl)(const Decl decl)
|
||||||
{
|
{
|
||||||
import ddoc.comments : parseComment;
|
import ddoc.comments : parseComment;
|
||||||
|
@ -164,7 +166,7 @@ private:
|
||||||
{
|
{
|
||||||
import std.string : format;
|
import std.string : format;
|
||||||
|
|
||||||
addErrorMessage(tokens, "dscanner.style.has_public_example", name is null
|
addErrorMessage(tokens, KEY, name is null
|
||||||
? "Public declaration has no documented example."
|
? "Public declaration has no documented example."
|
||||||
: format("Public declaration '%s' has no documented example.", name));
|
: format("Public declaration '%s' has no documented example.", name));
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ final class IfConstraintsIndentCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"if_constraints_indent";
|
mixin AnalyzerInfo!"if_constraints_indent";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Token)[] tokens, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
|
|
||||||
// convert tokens to a list of token starting positions per line
|
// convert tokens to a list of token starting positions per line
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ final class IfStatementCheck : BaseAnalyzer
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = BaseAnalyzer.visit;
|
||||||
mixin AnalyzerInfo!"redundant_if_check";
|
mixin AnalyzerInfo!"redundant_if_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const IfStatement ifStatement)
|
override void visit(const IfStatement ifStatement)
|
||||||
|
|
|
@ -26,9 +26,9 @@ final class IfElseSameCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"if_else_same_check";
|
mixin AnalyzerInfo!"if_else_same_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const IfStatement ifStatement)
|
override void visit(const IfStatement ifStatement)
|
||||||
|
@ -39,7 +39,7 @@ final class IfElseSameCheck : BaseAnalyzer
|
||||||
// extend 1 past, so we include the `else` token
|
// extend 1 past, so we include the `else` token
|
||||||
tokens = (tokens.ptr - 1)[0 .. tokens.length + 1];
|
tokens = (tokens.ptr - 1)[0 .. tokens.length + 1];
|
||||||
addErrorMessage(tokens,
|
addErrorMessage(tokens,
|
||||||
"dscanner.bugs.if_else_same", "'Else' branch is identical to 'Then' branch.");
|
IF_ELSE_SAME_KEY, "'Else' branch is identical to 'Then' branch.");
|
||||||
}
|
}
|
||||||
ifStatement.accept(this);
|
ifStatement.accept(this);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ final class IfElseSameCheck : BaseAnalyzer
|
||||||
if (e !is null && assignExpression.operator == tok!"="
|
if (e !is null && assignExpression.operator == tok!"="
|
||||||
&& e.ternaryExpression == assignExpression.ternaryExpression)
|
&& e.ternaryExpression == assignExpression.ternaryExpression)
|
||||||
{
|
{
|
||||||
addErrorMessage(assignExpression, "dscanner.bugs.self_assignment",
|
addErrorMessage(assignExpression, SELF_ASSIGNMENT_KEY,
|
||||||
"Left side of assignment operatior is identical to the right side.");
|
"Left side of assignment operatior is identical to the right side.");
|
||||||
}
|
}
|
||||||
assignExpression.accept(this);
|
assignExpression.accept(this);
|
||||||
|
@ -62,7 +62,7 @@ final class IfElseSameCheck : BaseAnalyzer
|
||||||
&& andAndExpression.left == andAndExpression.right)
|
&& andAndExpression.left == andAndExpression.right)
|
||||||
{
|
{
|
||||||
addErrorMessage(andAndExpression.right,
|
addErrorMessage(andAndExpression.right,
|
||||||
"dscanner.bugs.logic_operator_operands",
|
LOGIC_OPERATOR_OPERANDS_KEY,
|
||||||
"Left side of logical and is identical to right side.");
|
"Left side of logical and is identical to right side.");
|
||||||
}
|
}
|
||||||
andAndExpression.accept(this);
|
andAndExpression.accept(this);
|
||||||
|
@ -74,11 +74,17 @@ final class IfElseSameCheck : BaseAnalyzer
|
||||||
&& orOrExpression.left == orOrExpression.right)
|
&& orOrExpression.left == orOrExpression.right)
|
||||||
{
|
{
|
||||||
addErrorMessage(orOrExpression.right,
|
addErrorMessage(orOrExpression.right,
|
||||||
"dscanner.bugs.logic_operator_operands",
|
LOGIC_OPERATOR_OPERANDS_KEY,
|
||||||
"Left side of logical or is identical to right side.");
|
"Left side of logical or is identical to right side.");
|
||||||
}
|
}
|
||||||
orOrExpression.accept(this);
|
orOrExpression.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum string IF_ELSE_SAME_KEY = "dscanner.bugs.if_else_same";
|
||||||
|
enum string SELF_ASSIGNMENT_KEY = "dscanner.bugs.self_assignment";
|
||||||
|
enum string LOGIC_OPERATOR_OPERANDS_KEY = "dscanner.bugs.logic_operator_operands";
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -20,9 +20,9 @@ final class ImportSortednessCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"imports_sortedness";
|
mixin AnalyzerInfo!"imports_sortedness";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin ScopedVisit!Module;
|
mixin ScopedVisit!Module;
|
||||||
|
|
|
@ -22,9 +22,9 @@ final class IncorrectInfiniteRangeCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"incorrect_infinite_range_check";
|
mixin AnalyzerInfo!"incorrect_infinite_range_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const StructBody structBody)
|
override void visit(const StructBody structBody)
|
||||||
|
|
|
@ -13,23 +13,15 @@ import dscanner.analysis.helpers;
|
||||||
/**
|
/**
|
||||||
* Checks for labels and variables that have the same name.
|
* Checks for labels and variables that have the same name.
|
||||||
*/
|
*/
|
||||||
final class LabelVarNameCheck : BaseAnalyzer
|
final class LabelVarNameCheck : ScopedBaseAnalyzer
|
||||||
{
|
{
|
||||||
mixin AnalyzerInfo!"label_var_same_name_check";
|
mixin AnalyzerInfo!"label_var_same_name_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin ScopedVisit!Module;
|
|
||||||
mixin ScopedVisit!BlockStatement;
|
|
||||||
mixin ScopedVisit!StructBody;
|
|
||||||
mixin ScopedVisit!CaseStatement;
|
|
||||||
mixin ScopedVisit!ForStatement;
|
|
||||||
mixin ScopedVisit!IfStatement;
|
|
||||||
mixin ScopedVisit!TemplateDeclaration;
|
|
||||||
|
|
||||||
mixin AggregateVisit!ClassDeclaration;
|
mixin AggregateVisit!ClassDeclaration;
|
||||||
mixin AggregateVisit!StructDeclaration;
|
mixin AggregateVisit!StructDeclaration;
|
||||||
mixin AggregateVisit!InterfaceDeclaration;
|
mixin AggregateVisit!InterfaceDeclaration;
|
||||||
|
@ -64,10 +56,12 @@ final class LabelVarNameCheck : BaseAnalyzer
|
||||||
--conditionalDepth;
|
--conditionalDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = ScopedBaseAnalyzer.visit;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.suspicious.label_var_same_name";
|
||||||
|
|
||||||
Thing[string][] stack;
|
Thing[string][] stack;
|
||||||
|
|
||||||
template AggregateVisit(NodeType)
|
template AggregateVisit(NodeType)
|
||||||
|
@ -80,16 +74,6 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template ScopedVisit(NodeType)
|
|
||||||
{
|
|
||||||
override void visit(const NodeType n)
|
|
||||||
{
|
|
||||||
pushScope();
|
|
||||||
n.accept(this);
|
|
||||||
popScope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void duplicateCheck(const Token name, bool fromLabel, bool isConditional)
|
void duplicateCheck(const Token name, bool fromLabel, bool isConditional)
|
||||||
{
|
{
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
|
@ -106,7 +90,7 @@ private:
|
||||||
{
|
{
|
||||||
immutable thisKind = fromLabel ? "Label" : "Variable";
|
immutable thisKind = fromLabel ? "Label" : "Variable";
|
||||||
immutable otherKind = thing.isVar ? "variable" : "label";
|
immutable otherKind = thing.isVar ? "variable" : "label";
|
||||||
addErrorMessage(name, "dscanner.suspicious.label_var_same_name",
|
addErrorMessage(name, KEY,
|
||||||
thisKind ~ " \"" ~ fqn ~ "\" has the same name as a "
|
thisKind ~ " \"" ~ fqn ~ "\" has the same name as a "
|
||||||
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
|
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
|
||||||
}
|
}
|
||||||
|
@ -128,12 +112,12 @@ private:
|
||||||
return stack[$ - 1];
|
return stack[$ - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushScope()
|
protected override void pushScope()
|
||||||
{
|
{
|
||||||
stack.length++;
|
stack.length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void popScope()
|
protected override void popScope()
|
||||||
{
|
{
|
||||||
stack.length--;
|
stack.length--;
|
||||||
}
|
}
|
||||||
|
@ -278,6 +262,21 @@ unittest
|
||||||
struct a { int a; }
|
struct a { int a; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
switch (1) {
|
||||||
|
case 1:
|
||||||
|
int x, c1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
int x, c2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
int x, def;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}c, sac);
|
}c, sac);
|
||||||
stderr.writeln("Unittest for LabelVarNameCheck passed.");
|
stderr.writeln("Unittest for LabelVarNameCheck passed.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@ final class LambdaReturnCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"lambda_return_check";
|
mixin AnalyzerInfo!"lambda_return_check";
|
||||||
|
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const FunctionLiteralExpression fLit)
|
override void visit(const FunctionLiteralExpression fLit)
|
||||||
|
@ -49,8 +49,7 @@ final class LambdaReturnCheck : BaseAnalyzer
|
||||||
.concat(AutoFix.insertionAfter(fLit.tokens[0], ")"))
|
.concat(AutoFix.insertionAfter(fLit.tokens[0], ")"))
|
||||||
.concat(AutoFix.replacement(arrow[0], ""));
|
.concat(AutoFix.replacement(arrow[0], ""));
|
||||||
}
|
}
|
||||||
autofixes ~= AutoFix.insertionBefore(*endIncl, "(", "Add parenthesis (return delegate)")
|
autofixes ~= AutoFix.insertionBefore(*endIncl, "() ", "Add parenthesis (return delegate)");
|
||||||
.concat(AutoFix.insertionAfter(fe.specifiedFunctionBody.tokens[$ - 1], ")"));
|
|
||||||
addErrorMessage(tokens, KEY, "This lambda returns a lambda. Add parenthesis to clarify.",
|
addErrorMessage(tokens, KEY, "This lambda returns a lambda. Add parenthesis to clarify.",
|
||||||
autofixes);
|
autofixes);
|
||||||
}
|
}
|
||||||
|
@ -101,11 +100,11 @@ unittest
|
||||||
{
|
{
|
||||||
int[] b;
|
int[] b;
|
||||||
auto a = b.map!((a) { return a * a + 2; }).array(); // fix:0
|
auto a = b.map!((a) { return a * a + 2; }).array(); // fix:0
|
||||||
auto a = b.map!(a => ({ return a * a + 2; })).array(); // fix:1
|
auto a = b.map!(a => () { return a * a + 2; }).array(); // fix:1
|
||||||
pragma(msg, typeof((a) { return a; })); // fix:0
|
pragma(msg, typeof((a) { return a; })); // fix:0
|
||||||
pragma(msg, typeof(a => ({ return a; }))); // fix:1
|
pragma(msg, typeof(a => () { return a; })); // fix:1
|
||||||
pragma(msg, typeof((a) { return a; })); // fix:0
|
pragma(msg, typeof((a) { return a; })); // fix:0
|
||||||
pragma(msg, typeof((a) => ({ return a; }))); // fix:1
|
pragma(msg, typeof((a) => () { return a; })); // fix:1
|
||||||
}
|
}
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,15 @@ import dsymbol.scope_;
|
||||||
*/
|
*/
|
||||||
final class LengthSubtractionCheck : BaseAnalyzer
|
final class LengthSubtractionCheck : BaseAnalyzer
|
||||||
{
|
{
|
||||||
|
private enum string KEY = "dscanner.suspicious.length_subtraction";
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
mixin AnalyzerInfo!"length_subtraction_check";
|
mixin AnalyzerInfo!"length_subtraction_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AddExpression addExpression)
|
override void visit(const AddExpression addExpression)
|
||||||
|
@ -40,7 +42,7 @@ final class LengthSubtractionCheck : BaseAnalyzer
|
||||||
if (l.identifierOrTemplateInstance is null
|
if (l.identifierOrTemplateInstance is null
|
||||||
|| l.identifierOrTemplateInstance.identifier.text != "length")
|
|| l.identifierOrTemplateInstance.identifier.text != "length")
|
||||||
goto end;
|
goto end;
|
||||||
addErrorMessage(addExpression, "dscanner.suspicious.length_subtraction",
|
addErrorMessage(addExpression, KEY,
|
||||||
"Avoid subtracting from '.length' as it may be unsigned.",
|
"Avoid subtracting from '.length' as it may be unsigned.",
|
||||||
[
|
[
|
||||||
AutoFix.insertionBefore(l.tokens[0], "cast(ptrdiff_t) ", "Cast to ptrdiff_t")
|
AutoFix.insertionBefore(l.tokens[0], "cast(ptrdiff_t) ", "Cast to ptrdiff_t")
|
||||||
|
|
|
@ -20,10 +20,9 @@ final class LineLengthCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"long_line_check";
|
mixin AnalyzerInfo!"long_line_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Token)[] tokens, int maxLineLength, bool skipTests = false)
|
this(BaseAnalyzerArguments args, int maxLineLength)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
this.tokens = tokens;
|
|
||||||
this.maxLineLength = maxLineLength;
|
this.maxLineLength = maxLineLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +93,9 @@ private:
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
assert(new LineLengthCheck(null, null, 120).checkMultiLineToken(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 8);
|
assert(new LineLengthCheck(BaseAnalyzerArguments.init, 120).checkMultiLineToken(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 8);
|
||||||
assert(new LineLengthCheck(null, null, 120).checkMultiLineToken(Token(tok!"stringLiteral", " \na", 0, 0, 0)) == 2);
|
assert(new LineLengthCheck(BaseAnalyzerArguments.init, 120).checkMultiLineToken(Token(tok!"stringLiteral", " \na", 0, 0, 0)) == 2);
|
||||||
assert(new LineLengthCheck(null, null, 120).checkMultiLineToken(Token(tok!"stringLiteral", " \n ", 0, 0, 0)) == 5);
|
assert(new LineLengthCheck(BaseAnalyzerArguments.init, 120).checkMultiLineToken(Token(tok!"stringLiteral", " \n ", 0, 0, 0)) == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t tokenByteLength()(auto ref const Token tok)
|
static size_t tokenByteLength()(auto ref const Token tok)
|
||||||
|
@ -165,7 +164,6 @@ private:
|
||||||
|
|
||||||
enum string KEY = "dscanner.style.long_line";
|
enum string KEY = "dscanner.style.long_line";
|
||||||
const int maxLineLength;
|
const int maxLineLength;
|
||||||
const(Token)[] tokens;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@system unittest
|
@system unittest
|
||||||
|
|
|
@ -25,9 +25,9 @@ final class LocalImportCheck : BaseAnalyzer
|
||||||
/**
|
/**
|
||||||
* Construct with the given file name.
|
* Construct with the given file name.
|
||||||
*/
|
*/
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin visitThing!StructBody;
|
mixin visitThing!StructBody;
|
||||||
|
@ -59,7 +59,7 @@ final class LocalImportCheck : BaseAnalyzer
|
||||||
if (singleImport.rename.text.length == 0)
|
if (singleImport.rename.text.length == 0)
|
||||||
{
|
{
|
||||||
addErrorMessage(singleImport,
|
addErrorMessage(singleImport,
|
||||||
"dscanner.suspicious.local_imports", "Local imports should specify"
|
KEY, "Local imports should specify"
|
||||||
~ " the symbols being imported to avoid hiding local symbols.");
|
~ " the symbols being imported to avoid hiding local symbols.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,8 @@ final class LocalImportCheck : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.suspicious.local_imports";
|
||||||
|
|
||||||
mixin template visitThing(T)
|
mixin template visitThing(T)
|
||||||
{
|
{
|
||||||
override void visit(const T thing)
|
override void visit(const T thing)
|
||||||
|
|
|
@ -26,9 +26,9 @@ final class LogicPrecedenceCheck : BaseAnalyzer
|
||||||
enum string KEY = "dscanner.confusing.logical_precedence";
|
enum string KEY = "dscanner.confusing.logical_precedence";
|
||||||
mixin AnalyzerInfo!"logical_precedence_check";
|
mixin AnalyzerInfo!"logical_precedence_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const OrOrExpression orOr)
|
override void visit(const OrOrExpression orOr)
|
||||||
|
|
|
@ -14,9 +14,9 @@ final class MismatchedArgumentCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"mismatched_args_check";
|
mixin AnalyzerInfo!"mismatched_args_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const FunctionCallExpression fce)
|
override void visit(const FunctionCallExpression fce)
|
||||||
|
|
271
src/dscanner/analysis/nolint.d
Normal file
271
src/dscanner/analysis/nolint.d
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
module dscanner.analysis.nolint;
|
||||||
|
|
||||||
|
@safe:
|
||||||
|
|
||||||
|
import dparse.ast;
|
||||||
|
import dparse.lexer;
|
||||||
|
|
||||||
|
import std.algorithm : canFind;
|
||||||
|
import std.regex : matchAll, regex;
|
||||||
|
import std.string : lastIndexOf, strip;
|
||||||
|
import std.typecons;
|
||||||
|
|
||||||
|
struct NoLint
|
||||||
|
{
|
||||||
|
bool containsCheck(scope const(char)[] check) const
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (disabledChecks.get((() @trusted => cast(string) check)(), 0) > 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto dot = check.lastIndexOf('.');
|
||||||
|
if (dot == -1)
|
||||||
|
break;
|
||||||
|
check = check[0 .. dot];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// automatic pop when returned value goes out of scope
|
||||||
|
Poppable push(in Nullable!NoLint other) scope
|
||||||
|
{
|
||||||
|
if (other.isNull)
|
||||||
|
return Poppable(null);
|
||||||
|
|
||||||
|
foreach (key, value; other.get.getDisabledChecks)
|
||||||
|
this.disabledChecks[key] += value;
|
||||||
|
|
||||||
|
return Poppable(() => this.pop(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
package:
|
||||||
|
const(int[string]) getDisabledChecks() const
|
||||||
|
{
|
||||||
|
return this.disabledChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushCheck(in string check)
|
||||||
|
{
|
||||||
|
disabledChecks[check]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void merge(in Nullable!NoLint other)
|
||||||
|
{
|
||||||
|
if (other.isNull)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (key, value; other.get.getDisabledChecks)
|
||||||
|
this.disabledChecks[key] += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void pop(in Nullable!NoLint other)
|
||||||
|
{
|
||||||
|
if (other.isNull)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (key, value; other.get.getDisabledChecks)
|
||||||
|
{
|
||||||
|
assert(this.disabledChecks.get(key, 0) >= value);
|
||||||
|
|
||||||
|
this.disabledChecks[key] -= value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct Poppable
|
||||||
|
{
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
if (onPop)
|
||||||
|
onPop();
|
||||||
|
onPop = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void delegate() onPop;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[string] disabledChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoLintFactory
|
||||||
|
{
|
||||||
|
static Nullable!NoLint fromModuleDeclaration(in ModuleDeclaration moduleDeclaration)
|
||||||
|
{
|
||||||
|
NoLint noLint;
|
||||||
|
|
||||||
|
foreach (atAttribute; moduleDeclaration.atAttributes)
|
||||||
|
noLint.merge(NoLintFactory.fromAtAttribute(atAttribute));
|
||||||
|
|
||||||
|
if (!noLint.getDisabledChecks.length)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
return noLint.nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Nullable!NoLint fromDeclaration(in Declaration declaration)
|
||||||
|
{
|
||||||
|
NoLint noLint;
|
||||||
|
foreach (attribute; declaration.attributes)
|
||||||
|
noLint.merge(NoLintFactory.fromAttribute(attribute));
|
||||||
|
|
||||||
|
if (!noLint.getDisabledChecks.length)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
return noLint.nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Nullable!NoLint fromAttribute(const(Attribute) attribute)
|
||||||
|
{
|
||||||
|
if (attribute is null)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
return NoLintFactory.fromAtAttribute(attribute.atAttribute);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Nullable!NoLint fromAtAttribute(const(AtAttribute) atAttribute)
|
||||||
|
{
|
||||||
|
if (atAttribute is null)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
auto ident = atAttribute.identifier;
|
||||||
|
auto argumentList = atAttribute.argumentList;
|
||||||
|
|
||||||
|
if (argumentList !is null)
|
||||||
|
{
|
||||||
|
if (ident.text.length)
|
||||||
|
return NoLintFactory.fromStructUda(ident, argumentList);
|
||||||
|
else
|
||||||
|
return NoLintFactory.fromStringUda(argumentList);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return nullNoLint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @nolint("..")
|
||||||
|
static Nullable!NoLint fromStructUda(in Token ident, in ArgumentList argumentList)
|
||||||
|
in (ident.text.length && argumentList !is null)
|
||||||
|
{
|
||||||
|
if (ident.text != "nolint")
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
NoLint noLint;
|
||||||
|
|
||||||
|
foreach (nodeExpr; argumentList.items)
|
||||||
|
{
|
||||||
|
if (auto unaryExpr = cast(const UnaryExpression) nodeExpr)
|
||||||
|
{
|
||||||
|
auto primaryExpression = unaryExpr.primaryExpression;
|
||||||
|
if (primaryExpression is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (primaryExpression.primary != tok!"stringLiteral")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
noLint.pushCheck(primaryExpression.primary.text.strip("\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noLint.getDisabledChecks().length)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
return noLint.nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @("nolint(..)")
|
||||||
|
static Nullable!NoLint fromStringUda(in ArgumentList argumentList)
|
||||||
|
in (argumentList !is null)
|
||||||
|
{
|
||||||
|
NoLint noLint;
|
||||||
|
|
||||||
|
foreach (nodeExpr; argumentList.items)
|
||||||
|
{
|
||||||
|
if (auto unaryExpr = cast(const UnaryExpression) nodeExpr)
|
||||||
|
{
|
||||||
|
auto primaryExpression = unaryExpr.primaryExpression;
|
||||||
|
if (primaryExpression is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (primaryExpression.primary != tok!"stringLiteral")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto str = primaryExpression.primary.text.strip("\"");
|
||||||
|
Nullable!NoLint currNoLint = NoLintFactory.fromString(str);
|
||||||
|
noLint.merge(currNoLint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noLint.getDisabledChecks().length)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
return noLint.nullable;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform a string with form "nolint(abc, efg)"
|
||||||
|
// into a NoLint struct
|
||||||
|
static Nullable!NoLint fromString(in string str)
|
||||||
|
{
|
||||||
|
static immutable re = regex(`[\w-_.]+`, "g");
|
||||||
|
auto matches = matchAll(str, re);
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
const udaName = matches.hit;
|
||||||
|
if (udaName != "nolint")
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
matches.popFront;
|
||||||
|
|
||||||
|
NoLint noLint;
|
||||||
|
|
||||||
|
while (matches)
|
||||||
|
{
|
||||||
|
noLint.pushCheck(matches.hit);
|
||||||
|
matches.popFront;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noLint.getDisabledChecks.length)
|
||||||
|
return nullNoLint;
|
||||||
|
|
||||||
|
return noLint.nullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static nullNoLint = Nullable!NoLint.init;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
const s1 = "nolint(abc)";
|
||||||
|
const s2 = "nolint(abc, efg, hij)";
|
||||||
|
const s3 = " nolint ( abc , efg ) ";
|
||||||
|
const s4 = "nolint(dscanner.style.abc_efg-ijh)";
|
||||||
|
const s5 = "OtherUda(abc)";
|
||||||
|
const s6 = "nolint(dscanner)";
|
||||||
|
|
||||||
|
assert(NoLintFactory.fromString(s1).get.containsCheck("abc"));
|
||||||
|
|
||||||
|
assert(NoLintFactory.fromString(s2).get.containsCheck("abc"));
|
||||||
|
assert(NoLintFactory.fromString(s2).get.containsCheck("efg"));
|
||||||
|
assert(NoLintFactory.fromString(s2).get.containsCheck("hij"));
|
||||||
|
|
||||||
|
assert(NoLintFactory.fromString(s3).get.containsCheck("abc"));
|
||||||
|
assert(NoLintFactory.fromString(s3).get.containsCheck("efg"));
|
||||||
|
|
||||||
|
assert(NoLintFactory.fromString(s4).get.containsCheck("dscanner.style.abc_efg-ijh"));
|
||||||
|
|
||||||
|
assert(NoLintFactory.fromString(s5).isNull);
|
||||||
|
|
||||||
|
assert(NoLintFactory.fromString(s6).get.containsCheck("dscanner"));
|
||||||
|
assert(!NoLintFactory.fromString(s6).get.containsCheck("dscanner2"));
|
||||||
|
assert(NoLintFactory.fromString(s6).get.containsCheck("dscanner.foo"));
|
||||||
|
|
||||||
|
import std.stdio : stderr, writeln;
|
||||||
|
|
||||||
|
(() @trusted => stderr.writeln("Unittest for NoLint passed."))();
|
||||||
|
}
|
|
@ -26,9 +26,9 @@ public:
|
||||||
/**
|
/**
|
||||||
* Constructs the style checker with the given file name.
|
* Constructs the style checker with the given file name.
|
||||||
*/
|
*/
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Token t)
|
override void visit(const Token t)
|
||||||
|
@ -39,12 +39,15 @@ public:
|
||||||
&& ((t.text.startsWith("0b") && !t.text.matchFirst(badBinaryRegex)
|
&& ((t.text.startsWith("0b") && !t.text.matchFirst(badBinaryRegex)
|
||||||
.empty) || !t.text.matchFirst(badDecimalRegex).empty))
|
.empty) || !t.text.matchFirst(badDecimalRegex).empty))
|
||||||
{
|
{
|
||||||
addErrorMessage(t, "dscanner.style.number_literals",
|
addErrorMessage(t, KEY,
|
||||||
"Use underscores to improve number constant readability.");
|
"Use underscores to improve number constant readability.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.style.number_literals";
|
||||||
|
|
||||||
auto badBinaryRegex = ctRegex!(`^0b[01]{9,}`);
|
auto badBinaryRegex = ctRegex!(`^0b[01]{9,}`);
|
||||||
auto badDecimalRegex = ctRegex!(`^\d{5,}`);
|
auto badDecimalRegex = ctRegex!(`^\d{5,}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,9 @@ final class ObjectConstCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"object_const_check";
|
mixin AnalyzerInfo!"object_const_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
mixin visitTemplate!ClassDeclaration;
|
mixin visitTemplate!ClassDeclaration;
|
||||||
|
@ -68,7 +68,7 @@ final class ObjectConstCheck : BaseAnalyzer
|
||||||
if (inAggregate && !constColon && !constBlock && !isDeclationDisabled
|
if (inAggregate && !constColon && !constBlock && !isDeclationDisabled
|
||||||
&& isInteresting(fd.name.text) && !hasConst(fd.memberFunctionAttributes))
|
&& isInteresting(fd.name.text) && !hasConst(fd.memberFunctionAttributes))
|
||||||
{
|
{
|
||||||
addErrorMessage(d.functionDeclaration.name, "dscanner.suspicious.object_const",
|
addErrorMessage(d.functionDeclaration.name, KEY,
|
||||||
"Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.");
|
"Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,11 @@ final class ObjectConstCheck : BaseAnalyzer
|
||||||
constBlock = false;
|
constBlock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool hasConst(const MemberFunctionAttribute[] attributes)
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.suspicious.object_const";
|
||||||
|
|
||||||
|
static bool hasConst(const MemberFunctionAttribute[] attributes)
|
||||||
{
|
{
|
||||||
import std.algorithm : any;
|
import std.algorithm : any;
|
||||||
|
|
||||||
|
@ -89,15 +93,14 @@ final class ObjectConstCheck : BaseAnalyzer
|
||||||
|| a.tokenType == tok!"immutable" || a.tokenType == tok!"inout");
|
|| a.tokenType == tok!"immutable" || a.tokenType == tok!"inout");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool isInteresting(string name)
|
static bool isInteresting(string name)
|
||||||
{
|
{
|
||||||
return name == "opCmp" || name == "toHash" || name == "opEquals"
|
return name == "opCmp" || name == "toHash" || name == "opEquals"
|
||||||
|| name == "toString" || name == "opCast";
|
|| name == "toString" || name == "opCast";
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool constBlock;
|
bool constBlock;
|
||||||
private bool constColon;
|
bool constColon;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -23,9 +23,9 @@ final class OpEqualsWithoutToHashCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"opequals_tohash_check";
|
mixin AnalyzerInfo!"opequals_tohash_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const ClassDeclaration node)
|
override void visit(const ClassDeclaration node)
|
||||||
|
|
|
@ -31,9 +31,9 @@ final class PokemonExceptionCheck : BaseAnalyzer
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const LastCatch lc)
|
override void visit(const LastCatch lc)
|
||||||
|
|
|
@ -41,9 +41,9 @@ final class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"properly_documented_public_functions";
|
mixin AnalyzerInfo!"properly_documented_public_functions";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module mod)
|
override void visit(const Module mod)
|
||||||
|
|
|
@ -29,9 +29,9 @@ final class BackwardsRangeCheck : BaseAnalyzer
|
||||||
* Params:
|
* Params:
|
||||||
* fileName = the name of the file being analyzed
|
* fileName = the name of the file being analyzed
|
||||||
*/
|
*/
|
||||||
this(string fileName, const Scope* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const ForeachStatement foreachStatement)
|
override void visit(const ForeachStatement foreachStatement)
|
||||||
|
|
|
@ -17,13 +17,13 @@ import std.range : empty, front, walkLength;
|
||||||
/**
|
/**
|
||||||
* Checks for redundant attributes. At the moment only visibility attributes.
|
* Checks for redundant attributes. At the moment only visibility attributes.
|
||||||
*/
|
*/
|
||||||
final class RedundantAttributesCheck : BaseAnalyzer
|
final class RedundantAttributesCheck : ScopedBaseAnalyzer
|
||||||
{
|
{
|
||||||
mixin AnalyzerInfo!"redundant_attributes_check";
|
mixin AnalyzerInfo!"redundant_attributes_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
stack.length = 0;
|
stack.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,15 +67,8 @@ final class RedundantAttributesCheck : BaseAnalyzer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = ScopedBaseAnalyzer.visit;
|
||||||
|
|
||||||
mixin ScopedVisit!Module;
|
|
||||||
mixin ScopedVisit!BlockStatement;
|
|
||||||
mixin ScopedVisit!StructBody;
|
|
||||||
mixin ScopedVisit!CaseStatement;
|
|
||||||
mixin ScopedVisit!ForStatement;
|
|
||||||
mixin ScopedVisit!IfStatement;
|
|
||||||
mixin ScopedVisit!TemplateDeclaration;
|
|
||||||
mixin ScopedVisit!ConditionalDeclaration;
|
mixin ScopedVisit!ConditionalDeclaration;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -153,22 +146,12 @@ private:
|
||||||
return currentAttributes.map!(a => a.attribute.type.str).joiner(",").to!string;
|
return currentAttributes.map!(a => a.attribute.type.str).joiner(",").to!string;
|
||||||
}
|
}
|
||||||
|
|
||||||
template ScopedVisit(NodeType)
|
protected override void pushScope()
|
||||||
{
|
|
||||||
override void visit(const NodeType n)
|
|
||||||
{
|
|
||||||
pushScope();
|
|
||||||
n.accept(this);
|
|
||||||
popScope();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushScope()
|
|
||||||
{
|
{
|
||||||
stack.length++;
|
stack.length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void popScope()
|
protected override void popScope()
|
||||||
{
|
{
|
||||||
stack.length--;
|
stack.length--;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ final class RedundantParenCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"redundant_parens_check";
|
mixin AnalyzerInfo!"redundant_parens_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const IfStatement statement)
|
override void visit(const IfStatement statement)
|
||||||
|
|
|
@ -22,9 +22,9 @@ final class RedundantStorageClassCheck : BaseAnalyzer
|
||||||
enum string REDUNDANT_VARIABLE_ATTRIBUTES = "Variable declaration for `%s` has redundant attributes (%-(`%s`%|, %)).";
|
enum string REDUNDANT_VARIABLE_ATTRIBUTES = "Variable declaration for `%s` has redundant attributes (%-(`%s`%|, %)).";
|
||||||
mixin AnalyzerInfo!"redundant_storage_classes";
|
mixin AnalyzerInfo!"redundant_storage_classes";
|
||||||
|
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Declaration node)
|
override void visit(const Declaration node)
|
||||||
|
@ -59,9 +59,11 @@ final class RedundantStorageClassCheck : BaseAnalyzer
|
||||||
return;
|
return;
|
||||||
auto t = vd.declarators[0].name;
|
auto t = vd.declarators[0].name;
|
||||||
string message = REDUNDANT_VARIABLE_ATTRIBUTES.format(t.text, globalAttributes);
|
string message = REDUNDANT_VARIABLE_ATTRIBUTES.format(t.text, globalAttributes);
|
||||||
addErrorMessage(t, "dscanner.unnecessary.duplicate_attribute", message);
|
addErrorMessage(t, KEY, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum string KEY = "dscanner.unnecessary.duplicate_attribute";
|
||||||
}
|
}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
|
|
|
@ -73,6 +73,7 @@ import dscanner.analysis.final_attribute;
|
||||||
import dscanner.analysis.vcall_in_ctor;
|
import dscanner.analysis.vcall_in_ctor;
|
||||||
import dscanner.analysis.useless_initializer;
|
import dscanner.analysis.useless_initializer;
|
||||||
import dscanner.analysis.allman;
|
import dscanner.analysis.allman;
|
||||||
|
import dscanner.analysis.always_curly;
|
||||||
import dscanner.analysis.redundant_attributes;
|
import dscanner.analysis.redundant_attributes;
|
||||||
import dscanner.analysis.has_public_example;
|
import dscanner.analysis.has_public_example;
|
||||||
import dscanner.analysis.assert_without_msg;
|
import dscanner.analysis.assert_without_msg;
|
||||||
|
@ -133,7 +134,8 @@ private string formatContext(Message.Diagnostic diagnostic, scope const(char)[]
|
||||||
import std.string : indexOf, lastIndexOf;
|
import std.string : indexOf, lastIndexOf;
|
||||||
|
|
||||||
if (diagnostic.startIndex >= diagnostic.endIndex || diagnostic.endIndex > code.length
|
if (diagnostic.startIndex >= diagnostic.endIndex || diagnostic.endIndex > code.length
|
||||||
|| diagnostic.startColumn >= diagnostic.endColumn || diagnostic.endColumn == 0)
|
|| diagnostic.startColumn >= diagnostic.endColumn || diagnostic.endColumn == 0
|
||||||
|
|| diagnostic.startColumn == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
auto lineStart = code.lastIndexOf('\n', diagnostic.startIndex) + 1;
|
auto lineStart = code.lastIndexOf('\n', diagnostic.startIndex) + 1;
|
||||||
|
@ -305,7 +307,7 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config,
|
||||||
};
|
};
|
||||||
|
|
||||||
first = true;
|
first = true;
|
||||||
StatsCollector stats = new StatsCollector("");
|
StatsCollector stats = new StatsCollector(BaseAnalyzerArguments.init);
|
||||||
ulong lineOfCodeCount;
|
ulong lineOfCodeCount;
|
||||||
foreach (fileName; fileNames)
|
foreach (fileName; fileNames)
|
||||||
{
|
{
|
||||||
|
@ -611,12 +613,12 @@ private struct UserSelect
|
||||||
if (special.shorthands.canFind(input))
|
if (special.shorthands.canFind(input))
|
||||||
return special.id;
|
return special.id;
|
||||||
|
|
||||||
int item = input.to!int;
|
int item = input.to!int - 1;
|
||||||
if (item < 0 || item > regularItems.length)
|
if (item < 0 || item >= regularItems.length)
|
||||||
throw new Exception("Selected option number out of range.");
|
throw new Exception("Selected option number out of range.");
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
catch (ConvException e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
writeln("Invalid selection, try again. ", e.message);
|
writeln("Invalid selection, try again. ", e.message);
|
||||||
}
|
}
|
||||||
|
@ -747,216 +749,226 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
|
||||||
m.moduleDeclaration.moduleName.identifiers !is null)
|
m.moduleDeclaration.moduleName.identifiers !is null)
|
||||||
moduleName = m.moduleDeclaration.moduleName.identifiers.map!(e => e.text).join(".");
|
moduleName = m.moduleDeclaration.moduleName.identifiers.map!(e => e.text).join(".");
|
||||||
|
|
||||||
|
BaseAnalyzerArguments args = BaseAnalyzerArguments(
|
||||||
|
fileName,
|
||||||
|
tokens,
|
||||||
|
moduleScope
|
||||||
|
);
|
||||||
|
|
||||||
if (moduleName.shouldRun!AsmStyleCheck(analysisConfig))
|
if (moduleName.shouldRun!AsmStyleCheck(analysisConfig))
|
||||||
checks ~= new AsmStyleCheck(fileName, moduleScope,
|
checks ~= new AsmStyleCheck(args.setSkipTests(
|
||||||
analysisConfig.asm_style_check == Check.skipTests && !ut);
|
analysisConfig.asm_style_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!BackwardsRangeCheck(analysisConfig))
|
if (moduleName.shouldRun!BackwardsRangeCheck(analysisConfig))
|
||||||
checks ~= new BackwardsRangeCheck(fileName, moduleScope,
|
checks ~= new BackwardsRangeCheck(args.setSkipTests(
|
||||||
analysisConfig.backwards_range_check == Check.skipTests && !ut);
|
analysisConfig.backwards_range_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!BuiltinPropertyNameCheck(analysisConfig))
|
if (moduleName.shouldRun!BuiltinPropertyNameCheck(analysisConfig))
|
||||||
checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope,
|
checks ~= new BuiltinPropertyNameCheck(args.setSkipTests(
|
||||||
analysisConfig.builtin_property_names_check == Check.skipTests && !ut);
|
analysisConfig.builtin_property_names_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!CommaExpressionCheck(analysisConfig))
|
if (moduleName.shouldRun!CommaExpressionCheck(analysisConfig))
|
||||||
checks ~= new CommaExpressionCheck(fileName, moduleScope,
|
checks ~= new CommaExpressionCheck(args.setSkipTests(
|
||||||
analysisConfig.comma_expression_check == Check.skipTests && !ut);
|
analysisConfig.comma_expression_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!ConstructorCheck(analysisConfig))
|
if (moduleName.shouldRun!ConstructorCheck(analysisConfig))
|
||||||
checks ~= new ConstructorCheck(fileName, moduleScope,
|
checks ~= new ConstructorCheck(args.setSkipTests(
|
||||||
analysisConfig.constructor_check == Check.skipTests && !ut);
|
analysisConfig.constructor_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnmodifiedFinder(analysisConfig))
|
if (moduleName.shouldRun!UnmodifiedFinder(analysisConfig))
|
||||||
checks ~= new UnmodifiedFinder(fileName, moduleScope,
|
checks ~= new UnmodifiedFinder(args.setSkipTests(
|
||||||
analysisConfig.could_be_immutable_check == Check.skipTests && !ut);
|
analysisConfig.could_be_immutable_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!DeleteCheck(analysisConfig))
|
if (moduleName.shouldRun!DeleteCheck(analysisConfig))
|
||||||
checks ~= new DeleteCheck(fileName, moduleScope,
|
checks ~= new DeleteCheck(args.setSkipTests(
|
||||||
analysisConfig.delete_check == Check.skipTests && !ut);
|
analysisConfig.delete_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!DuplicateAttributeCheck(analysisConfig))
|
if (moduleName.shouldRun!DuplicateAttributeCheck(analysisConfig))
|
||||||
checks ~= new DuplicateAttributeCheck(fileName, moduleScope,
|
checks ~= new DuplicateAttributeCheck(args.setSkipTests(
|
||||||
analysisConfig.duplicate_attribute == Check.skipTests && !ut);
|
analysisConfig.duplicate_attribute == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!EnumArrayLiteralCheck(analysisConfig))
|
if (moduleName.shouldRun!EnumArrayLiteralCheck(analysisConfig))
|
||||||
checks ~= new EnumArrayLiteralCheck(fileName, moduleScope,
|
checks ~= new EnumArrayLiteralCheck(args.setSkipTests(
|
||||||
analysisConfig.enum_array_literal_check == Check.skipTests && !ut);
|
analysisConfig.enum_array_literal_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!PokemonExceptionCheck(analysisConfig))
|
if (moduleName.shouldRun!PokemonExceptionCheck(analysisConfig))
|
||||||
checks ~= new PokemonExceptionCheck(fileName, moduleScope,
|
checks ~= new PokemonExceptionCheck(args.setSkipTests(
|
||||||
analysisConfig.exception_check == Check.skipTests && !ut);
|
analysisConfig.exception_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!FloatOperatorCheck(analysisConfig))
|
if (moduleName.shouldRun!FloatOperatorCheck(analysisConfig))
|
||||||
checks ~= new FloatOperatorCheck(fileName, moduleScope,
|
checks ~= new FloatOperatorCheck(args.setSkipTests(
|
||||||
analysisConfig.float_operator_check == Check.skipTests && !ut);
|
analysisConfig.float_operator_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!FunctionAttributeCheck(analysisConfig))
|
if (moduleName.shouldRun!FunctionAttributeCheck(analysisConfig))
|
||||||
checks ~= new FunctionAttributeCheck(fileName, moduleScope,
|
checks ~= new FunctionAttributeCheck(args.setSkipTests(
|
||||||
analysisConfig.function_attribute_check == Check.skipTests && !ut);
|
analysisConfig.function_attribute_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!IfElseSameCheck(analysisConfig))
|
if (moduleName.shouldRun!IfElseSameCheck(analysisConfig))
|
||||||
checks ~= new IfElseSameCheck(fileName, moduleScope,
|
checks ~= new IfElseSameCheck(args.setSkipTests(
|
||||||
analysisConfig.if_else_same_check == Check.skipTests&& !ut);
|
analysisConfig.if_else_same_check == Check.skipTests&& !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LabelVarNameCheck(analysisConfig))
|
if (moduleName.shouldRun!LabelVarNameCheck(analysisConfig))
|
||||||
checks ~= new LabelVarNameCheck(fileName, moduleScope,
|
checks ~= new LabelVarNameCheck(args.setSkipTests(
|
||||||
analysisConfig.label_var_same_name_check == Check.skipTests && !ut);
|
analysisConfig.label_var_same_name_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LengthSubtractionCheck(analysisConfig))
|
if (moduleName.shouldRun!LengthSubtractionCheck(analysisConfig))
|
||||||
checks ~= new LengthSubtractionCheck(fileName, moduleScope,
|
checks ~= new LengthSubtractionCheck(args.setSkipTests(
|
||||||
analysisConfig.length_subtraction_check == Check.skipTests && !ut);
|
analysisConfig.length_subtraction_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LocalImportCheck(analysisConfig))
|
if (moduleName.shouldRun!LocalImportCheck(analysisConfig))
|
||||||
checks ~= new LocalImportCheck(fileName, moduleScope,
|
checks ~= new LocalImportCheck(args.setSkipTests(
|
||||||
analysisConfig.local_import_check == Check.skipTests && !ut);
|
analysisConfig.local_import_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LogicPrecedenceCheck(analysisConfig))
|
if (moduleName.shouldRun!LogicPrecedenceCheck(analysisConfig))
|
||||||
checks ~= new LogicPrecedenceCheck(fileName, moduleScope,
|
checks ~= new LogicPrecedenceCheck(args.setSkipTests(
|
||||||
analysisConfig.logical_precedence_check == Check.skipTests && !ut);
|
analysisConfig.logical_precedence_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!MismatchedArgumentCheck(analysisConfig))
|
if (moduleName.shouldRun!MismatchedArgumentCheck(analysisConfig))
|
||||||
checks ~= new MismatchedArgumentCheck(fileName, moduleScope,
|
checks ~= new MismatchedArgumentCheck(args.setSkipTests(
|
||||||
analysisConfig.mismatched_args_check == Check.skipTests && !ut);
|
analysisConfig.mismatched_args_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!NumberStyleCheck(analysisConfig))
|
if (moduleName.shouldRun!NumberStyleCheck(analysisConfig))
|
||||||
checks ~= new NumberStyleCheck(fileName, moduleScope,
|
checks ~= new NumberStyleCheck(args.setSkipTests(
|
||||||
analysisConfig.number_style_check == Check.skipTests && !ut);
|
analysisConfig.number_style_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!ObjectConstCheck(analysisConfig))
|
if (moduleName.shouldRun!ObjectConstCheck(analysisConfig))
|
||||||
checks ~= new ObjectConstCheck(fileName, moduleScope,
|
checks ~= new ObjectConstCheck(args.setSkipTests(
|
||||||
analysisConfig.object_const_check == Check.skipTests && !ut);
|
analysisConfig.object_const_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!OpEqualsWithoutToHashCheck(analysisConfig))
|
if (moduleName.shouldRun!OpEqualsWithoutToHashCheck(analysisConfig))
|
||||||
checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope,
|
checks ~= new OpEqualsWithoutToHashCheck(args.setSkipTests(
|
||||||
analysisConfig.opequals_tohash_check == Check.skipTests && !ut);
|
analysisConfig.opequals_tohash_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!RedundantParenCheck(analysisConfig))
|
if (moduleName.shouldRun!RedundantParenCheck(analysisConfig))
|
||||||
checks ~= new RedundantParenCheck(fileName, moduleScope,
|
checks ~= new RedundantParenCheck(args.setSkipTests(
|
||||||
analysisConfig.redundant_parens_check == Check.skipTests && !ut);
|
analysisConfig.redundant_parens_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!StyleChecker(analysisConfig))
|
if (moduleName.shouldRun!StyleChecker(analysisConfig))
|
||||||
checks ~= new StyleChecker(fileName, moduleScope,
|
checks ~= new StyleChecker(args.setSkipTests(
|
||||||
analysisConfig.style_check == Check.skipTests && !ut);
|
analysisConfig.style_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UndocumentedDeclarationCheck(analysisConfig))
|
if (moduleName.shouldRun!UndocumentedDeclarationCheck(analysisConfig))
|
||||||
checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope,
|
checks ~= new UndocumentedDeclarationCheck(args.setSkipTests(
|
||||||
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut);
|
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnusedLabelCheck(analysisConfig))
|
if (moduleName.shouldRun!UnusedLabelCheck(analysisConfig))
|
||||||
checks ~= new UnusedLabelCheck(fileName, moduleScope,
|
checks ~= new UnusedLabelCheck(args.setSkipTests(
|
||||||
analysisConfig.unused_label_check == Check.skipTests && !ut);
|
analysisConfig.unused_label_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnusedVariableCheck(analysisConfig))
|
if (moduleName.shouldRun!UnusedVariableCheck(analysisConfig))
|
||||||
checks ~= new UnusedVariableCheck(fileName, moduleScope,
|
checks ~= new UnusedVariableCheck(args.setSkipTests(
|
||||||
analysisConfig.unused_variable_check == Check.skipTests && !ut);
|
analysisConfig.unused_variable_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnusedParameterCheck(analysisConfig))
|
if (moduleName.shouldRun!UnusedParameterCheck(analysisConfig))
|
||||||
checks ~= new UnusedParameterCheck(fileName, moduleScope,
|
checks ~= new UnusedParameterCheck(args.setSkipTests(
|
||||||
analysisConfig.unused_parameter_check == Check.skipTests && !ut);
|
analysisConfig.unused_parameter_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LineLengthCheck(analysisConfig))
|
if (moduleName.shouldRun!LineLengthCheck(analysisConfig))
|
||||||
checks ~= new LineLengthCheck(fileName, tokens,
|
checks ~= new LineLengthCheck(args.setSkipTests(
|
||||||
analysisConfig.max_line_length,
|
analysisConfig.long_line_check == Check.skipTests && !ut),
|
||||||
analysisConfig.long_line_check == Check.skipTests && !ut);
|
analysisConfig.max_line_length);
|
||||||
|
|
||||||
if (moduleName.shouldRun!AutoRefAssignmentCheck(analysisConfig))
|
if (moduleName.shouldRun!AutoRefAssignmentCheck(analysisConfig))
|
||||||
checks ~= new AutoRefAssignmentCheck(fileName,
|
checks ~= new AutoRefAssignmentCheck(args.setSkipTests(
|
||||||
analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut);
|
analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!IncorrectInfiniteRangeCheck(analysisConfig))
|
if (moduleName.shouldRun!IncorrectInfiniteRangeCheck(analysisConfig))
|
||||||
checks ~= new IncorrectInfiniteRangeCheck(fileName,
|
checks ~= new IncorrectInfiniteRangeCheck(args.setSkipTests(
|
||||||
analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut);
|
analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UselessAssertCheck(analysisConfig))
|
if (moduleName.shouldRun!UselessAssertCheck(analysisConfig))
|
||||||
checks ~= new UselessAssertCheck(fileName,
|
checks ~= new UselessAssertCheck(args.setSkipTests(
|
||||||
analysisConfig.useless_assert_check == Check.skipTests && !ut);
|
analysisConfig.useless_assert_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!AliasSyntaxCheck(analysisConfig))
|
if (moduleName.shouldRun!AliasSyntaxCheck(analysisConfig))
|
||||||
checks ~= new AliasSyntaxCheck(fileName,
|
checks ~= new AliasSyntaxCheck(args.setSkipTests(
|
||||||
analysisConfig.alias_syntax_check == Check.skipTests && !ut);
|
analysisConfig.alias_syntax_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!StaticIfElse(analysisConfig))
|
if (moduleName.shouldRun!StaticIfElse(analysisConfig))
|
||||||
checks ~= new StaticIfElse(fileName,
|
checks ~= new StaticIfElse(args.setSkipTests(
|
||||||
analysisConfig.static_if_else_check == Check.skipTests && !ut);
|
analysisConfig.static_if_else_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LambdaReturnCheck(analysisConfig))
|
if (moduleName.shouldRun!LambdaReturnCheck(analysisConfig))
|
||||||
checks ~= new LambdaReturnCheck(fileName,
|
checks ~= new LambdaReturnCheck(args.setSkipTests(
|
||||||
analysisConfig.lambda_return_check == Check.skipTests && !ut);
|
analysisConfig.lambda_return_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!AutoFunctionChecker(analysisConfig))
|
if (moduleName.shouldRun!AutoFunctionChecker(analysisConfig))
|
||||||
checks ~= new AutoFunctionChecker(fileName,
|
checks ~= new AutoFunctionChecker(args.setSkipTests(
|
||||||
analysisConfig.auto_function_check == Check.skipTests && !ut);
|
analysisConfig.auto_function_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!ImportSortednessCheck(analysisConfig))
|
if (moduleName.shouldRun!ImportSortednessCheck(analysisConfig))
|
||||||
checks ~= new ImportSortednessCheck(fileName,
|
checks ~= new ImportSortednessCheck(args.setSkipTests(
|
||||||
analysisConfig.imports_sortedness == Check.skipTests && !ut);
|
analysisConfig.imports_sortedness == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!ExplicitlyAnnotatedUnittestCheck(analysisConfig))
|
if (moduleName.shouldRun!ExplicitlyAnnotatedUnittestCheck(analysisConfig))
|
||||||
checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName,
|
checks ~= new ExplicitlyAnnotatedUnittestCheck(args.setSkipTests(
|
||||||
analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut);
|
analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!ProperlyDocumentedPublicFunctions(analysisConfig))
|
if (moduleName.shouldRun!ProperlyDocumentedPublicFunctions(analysisConfig))
|
||||||
checks ~= new ProperlyDocumentedPublicFunctions(fileName,
|
checks ~= new ProperlyDocumentedPublicFunctions(args.setSkipTests(
|
||||||
analysisConfig.properly_documented_public_functions == Check.skipTests && !ut);
|
analysisConfig.properly_documented_public_functions == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!FinalAttributeChecker(analysisConfig))
|
if (moduleName.shouldRun!FinalAttributeChecker(analysisConfig))
|
||||||
checks ~= new FinalAttributeChecker(fileName,
|
checks ~= new FinalAttributeChecker(args.setSkipTests(
|
||||||
analysisConfig.final_attribute_check == Check.skipTests && !ut);
|
analysisConfig.final_attribute_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!VcallCtorChecker(analysisConfig))
|
if (moduleName.shouldRun!VcallCtorChecker(analysisConfig))
|
||||||
checks ~= new VcallCtorChecker(fileName,
|
checks ~= new VcallCtorChecker(args.setSkipTests(
|
||||||
analysisConfig.vcall_in_ctor == Check.skipTests && !ut);
|
analysisConfig.vcall_in_ctor == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UselessInitializerChecker(analysisConfig))
|
if (moduleName.shouldRun!UselessInitializerChecker(analysisConfig))
|
||||||
checks ~= new UselessInitializerChecker(fileName,
|
checks ~= new UselessInitializerChecker(args.setSkipTests(
|
||||||
analysisConfig.useless_initializer == Check.skipTests && !ut);
|
analysisConfig.useless_initializer == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!AllManCheck(analysisConfig))
|
if (moduleName.shouldRun!AllManCheck(analysisConfig))
|
||||||
checks ~= new AllManCheck(fileName, tokens,
|
checks ~= new AllManCheck(args.setSkipTests(
|
||||||
analysisConfig.allman_braces_check == Check.skipTests && !ut);
|
analysisConfig.allman_braces_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
|
if (moduleName.shouldRun!AlwaysCurlyCheck(analysisConfig))
|
||||||
|
checks ~= new AlwaysCurlyCheck(args.setSkipTests(
|
||||||
|
analysisConfig.always_curly_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!RedundantAttributesCheck(analysisConfig))
|
if (moduleName.shouldRun!RedundantAttributesCheck(analysisConfig))
|
||||||
checks ~= new RedundantAttributesCheck(fileName, moduleScope,
|
checks ~= new RedundantAttributesCheck(args.setSkipTests(
|
||||||
analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
|
analysisConfig.redundant_attributes_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!HasPublicExampleCheck(analysisConfig))
|
if (moduleName.shouldRun!HasPublicExampleCheck(analysisConfig))
|
||||||
checks ~= new HasPublicExampleCheck(fileName, moduleScope,
|
checks ~= new HasPublicExampleCheck(args.setSkipTests(
|
||||||
analysisConfig.has_public_example == Check.skipTests && !ut);
|
analysisConfig.has_public_example == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!AssertWithoutMessageCheck(analysisConfig))
|
if (moduleName.shouldRun!AssertWithoutMessageCheck(analysisConfig))
|
||||||
checks ~= new AssertWithoutMessageCheck(fileName, moduleScope,
|
checks ~= new AssertWithoutMessageCheck(args.setSkipTests(
|
||||||
analysisConfig.assert_without_msg == Check.skipTests && !ut);
|
analysisConfig.assert_without_msg == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!IfConstraintsIndentCheck(analysisConfig))
|
if (moduleName.shouldRun!IfConstraintsIndentCheck(analysisConfig))
|
||||||
checks ~= new IfConstraintsIndentCheck(fileName, tokens,
|
checks ~= new IfConstraintsIndentCheck(args.setSkipTests(
|
||||||
analysisConfig.if_constraints_indent == Check.skipTests && !ut);
|
analysisConfig.if_constraints_indent == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!TrustTooMuchCheck(analysisConfig))
|
if (moduleName.shouldRun!TrustTooMuchCheck(analysisConfig))
|
||||||
checks ~= new TrustTooMuchCheck(fileName,
|
checks ~= new TrustTooMuchCheck(args.setSkipTests(
|
||||||
analysisConfig.trust_too_much == Check.skipTests && !ut);
|
analysisConfig.trust_too_much == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!RedundantStorageClassCheck(analysisConfig))
|
if (moduleName.shouldRun!RedundantStorageClassCheck(analysisConfig))
|
||||||
checks ~= new RedundantStorageClassCheck(fileName,
|
checks ~= new RedundantStorageClassCheck(args.setSkipTests(
|
||||||
analysisConfig.redundant_storage_classes == Check.skipTests && !ut);
|
analysisConfig.redundant_storage_classes == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnusedResultChecker(analysisConfig))
|
if (moduleName.shouldRun!UnusedResultChecker(analysisConfig))
|
||||||
checks ~= new UnusedResultChecker(fileName, moduleScope,
|
checks ~= new UnusedResultChecker(args.setSkipTests(
|
||||||
analysisConfig.unused_result == Check.skipTests && !ut);
|
analysisConfig.unused_result == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!CyclomaticComplexityCheck(analysisConfig))
|
if (moduleName.shouldRun!CyclomaticComplexityCheck(analysisConfig))
|
||||||
checks ~= new CyclomaticComplexityCheck(fileName, moduleScope,
|
checks ~= new CyclomaticComplexityCheck(args.setSkipTests(
|
||||||
analysisConfig.cyclomatic_complexity == Check.skipTests && !ut,
|
analysisConfig.cyclomatic_complexity == Check.skipTests && !ut),
|
||||||
analysisConfig.max_cyclomatic_complexity.to!int);
|
analysisConfig.max_cyclomatic_complexity.to!int);
|
||||||
|
|
||||||
if (moduleName.shouldRun!BodyOnDisabledFuncsCheck(analysisConfig))
|
if (moduleName.shouldRun!BodyOnDisabledFuncsCheck(analysisConfig))
|
||||||
checks ~= new BodyOnDisabledFuncsCheck(fileName, moduleScope,
|
checks ~= new BodyOnDisabledFuncsCheck(args.setSkipTests(
|
||||||
analysisConfig.body_on_disabled_func_check == Check.skipTests && !ut);
|
analysisConfig.body_on_disabled_func_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
version (none)
|
version (none)
|
||||||
if (moduleName.shouldRun!IfStatementCheck(analysisConfig))
|
if (moduleName.shouldRun!IfStatementCheck(analysisConfig))
|
||||||
checks ~= new IfStatementCheck(fileName, moduleScope,
|
checks ~= new IfStatementCheck(args.setSkipTests(
|
||||||
analysisConfig.redundant_if_check == Check.skipTests && !ut);
|
analysisConfig.redundant_if_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
return checks;
|
return checks;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ final class StaticIfElse : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"static_if_else_check";
|
mixin AnalyzerInfo!"static_if_else_check";
|
||||||
|
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const ConditionalStatement cc)
|
override void visit(const ConditionalStatement cc)
|
||||||
|
|
|
@ -13,9 +13,10 @@ final class StatsCollector : BaseAnalyzer
|
||||||
{
|
{
|
||||||
alias visit = ASTVisitor.visit;
|
alias visit = ASTVisitor.visit;
|
||||||
|
|
||||||
this(string fileName)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null);
|
args.skipTests = false; // old behavior compatibility
|
||||||
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Statement statement)
|
override void visit(const Statement statement)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import std.conv;
|
||||||
import std.format;
|
import std.format;
|
||||||
import dscanner.analysis.helpers;
|
import dscanner.analysis.helpers;
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.analysis.nolint;
|
||||||
import dsymbol.scope_ : Scope;
|
import dsymbol.scope_ : Scope;
|
||||||
|
|
||||||
final class StyleChecker : BaseAnalyzer
|
final class StyleChecker : BaseAnalyzer
|
||||||
|
@ -26,13 +27,16 @@ final class StyleChecker : BaseAnalyzer
|
||||||
enum string KEY = "dscanner.style.phobos_naming_convention";
|
enum string KEY = "dscanner.style.phobos_naming_convention";
|
||||||
mixin AnalyzerInfo!"style_check";
|
mixin AnalyzerInfo!"style_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const ModuleDeclaration dec)
|
override void visit(const ModuleDeclaration dec)
|
||||||
{
|
{
|
||||||
|
with (noLint.push(NoLintFactory.fromModuleDeclaration(dec)))
|
||||||
|
dec.accept(this);
|
||||||
|
|
||||||
foreach (part; dec.moduleName.identifiers)
|
foreach (part; dec.moduleName.identifiers)
|
||||||
{
|
{
|
||||||
if (part.text.matchFirst(moduleNameRegex).length == 0)
|
if (part.text.matchFirst(moduleNameRegex).length == 0)
|
||||||
|
|
|
@ -31,9 +31,9 @@ public:
|
||||||
mixin AnalyzerInfo!"trust_too_much";
|
mixin AnalyzerInfo!"trust_too_much";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AtAttribute d)
|
override void visit(const AtAttribute d)
|
||||||
|
|
|
@ -23,9 +23,9 @@ final class UndocumentedDeclarationCheck : BaseAnalyzer
|
||||||
|
|
||||||
mixin AnalyzerInfo!"undocumented_declaration_check";
|
mixin AnalyzerInfo!"undocumented_declaration_check";
|
||||||
|
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module mod)
|
override void visit(const Module mod)
|
||||||
|
@ -146,6 +146,8 @@ final class UndocumentedDeclarationCheck : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.style.undocumented_declaration";
|
||||||
|
|
||||||
mixin template V(T)
|
mixin template V(T)
|
||||||
{
|
{
|
||||||
override void visit(const T declaration)
|
override void visit(const T declaration)
|
||||||
|
@ -223,7 +225,7 @@ private:
|
||||||
{
|
{
|
||||||
import std.string : format;
|
import std.string : format;
|
||||||
|
|
||||||
addErrorMessage(range, "dscanner.style.undocumented_declaration", name is null
|
addErrorMessage(range, KEY, name is null
|
||||||
? "Public declaration is undocumented."
|
? "Public declaration is undocumented."
|
||||||
: format("Public declaration '%s' is undocumented.", name));
|
: format("Public declaration '%s' is undocumented.", name));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
module dscanner.analysis.unmodified;
|
module dscanner.analysis.unmodified;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.analysis.nolint;
|
||||||
import dscanner.utils : safeAccess;
|
import dscanner.utils : safeAccess;
|
||||||
import dsymbol.scope_ : Scope;
|
import dsymbol.scope_ : Scope;
|
||||||
import std.container;
|
import std.container;
|
||||||
|
@ -21,9 +22,9 @@ final class UnmodifiedFinder : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"could_be_immutable_check";
|
mixin AnalyzerInfo!"could_be_immutable_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module mod)
|
override void visit(const Module mod)
|
||||||
|
@ -114,11 +115,15 @@ final class UnmodifiedFinder : BaseAnalyzer
|
||||||
if (canFindImmutableOrConst(dec))
|
if (canFindImmutableOrConst(dec))
|
||||||
{
|
{
|
||||||
isImmutable++;
|
isImmutable++;
|
||||||
dec.accept(this);
|
with (noLint.push(NoLintFactory.fromDeclaration(dec)))
|
||||||
|
dec.accept(this);
|
||||||
isImmutable--;
|
isImmutable--;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
dec.accept(this);
|
{
|
||||||
|
with (noLint.push(NoLintFactory.fromDeclaration(dec)))
|
||||||
|
dec.accept(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const IdentifierChain ic)
|
override void visit(const IdentifierChain ic)
|
||||||
|
@ -189,6 +194,8 @@ final class UnmodifiedFinder : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.suspicious.unmodified";
|
||||||
|
|
||||||
template PartsMightModify(T)
|
template PartsMightModify(T)
|
||||||
{
|
{
|
||||||
override void visit(const T t)
|
override void visit(const T t)
|
||||||
|
@ -300,7 +307,7 @@ private:
|
||||||
{
|
{
|
||||||
immutable string errorMessage = "Variable " ~ vi.name
|
immutable string errorMessage = "Variable " ~ vi.name
|
||||||
~ " is never modified and could have been declared const or immutable.";
|
~ " is never modified and could have been declared const or immutable.";
|
||||||
addErrorMessage(vi.token, "dscanner.suspicious.unmodified", errorMessage);
|
addErrorMessage(vi.token, KEY, errorMessage);
|
||||||
}
|
}
|
||||||
tree = tree[0 .. $ - 1];
|
tree = tree[0 .. $ - 1];
|
||||||
}
|
}
|
||||||
|
@ -379,5 +386,12 @@ bool isValueTypeSimple(const Type type) pure nothrow @nogc
|
||||||
foo(i2);
|
foo(i2);
|
||||||
}
|
}
|
||||||
}, sac);
|
}, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@("nolint(dscanner.suspicious.unmodified)")
|
||||||
|
void foo(){
|
||||||
|
int i = 1;
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,10 @@ abstract class UnusedIdentifierCheck : BaseAnalyzer
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = BaseAnalyzer.visit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
|
||||||
* fileName = the name of the file being analyzed
|
|
||||||
*/
|
*/
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
re = regex("[\\p{Alphabetic}_][\\w_]*");
|
re = regex("[\\p{Alphabetic}_][\\w_]*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +77,13 @@ abstract class UnusedIdentifierCheck : BaseAnalyzer
|
||||||
mixin PartsUseVariables!ThrowExpression;
|
mixin PartsUseVariables!ThrowExpression;
|
||||||
mixin PartsUseVariables!CastExpression;
|
mixin PartsUseVariables!CastExpression;
|
||||||
|
|
||||||
|
override void dynamicDispatch(const ExpressionNode n)
|
||||||
|
{
|
||||||
|
interestDepth++;
|
||||||
|
super.dynamicDispatch(n);
|
||||||
|
interestDepth--;
|
||||||
|
}
|
||||||
|
|
||||||
override void visit(const SwitchStatement switchStatement)
|
override void visit(const SwitchStatement switchStatement)
|
||||||
{
|
{
|
||||||
if (switchStatement.expression !is null)
|
if (switchStatement.expression !is null)
|
||||||
|
@ -414,15 +419,13 @@ abstract class UnusedStorageCheck : UnusedIdentifierCheck
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* fileName = the name of the file being analyzed
|
* args = commonly shared analyzer arguments
|
||||||
* sc = the scope
|
* publicType = declaration kind used in error messages, e.g. "Variable"s
|
||||||
* skipTest = whether tests should be analyzed
|
* reportType = declaration kind used in error reports, e.g. "unused_variable"
|
||||||
* publicType = declaration kind used in error messages, e.g. "Variable"s
|
|
||||||
* reportType = declaration kind used in error reports, e.g. "unused_variable"
|
|
||||||
*/
|
*/
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false, string publicType = null, string reportType = null)
|
this(BaseAnalyzerArguments args, string publicType = null, string reportType = null)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
this.publicType = publicType;
|
this.publicType = publicType;
|
||||||
this.reportType = reportType;
|
this.reportType = reportType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,9 @@ final class UnusedLabelCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"unused_label_check";
|
mixin AnalyzerInfo!"unused_label_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module mod)
|
override void visit(const Module mod)
|
||||||
|
@ -115,6 +115,8 @@ final class UnusedLabelCheck : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
enum string KEY = "dscanner.suspicious.unused_label";
|
||||||
|
|
||||||
static struct Label
|
static struct Label
|
||||||
{
|
{
|
||||||
string name;
|
string name;
|
||||||
|
@ -144,7 +146,7 @@ private:
|
||||||
}
|
}
|
||||||
else if (!label.used)
|
else if (!label.used)
|
||||||
{
|
{
|
||||||
addErrorMessage(label.token, "dscanner.suspicious.unused_label",
|
addErrorMessage(label.token, KEY,
|
||||||
"Label \"" ~ label.name ~ "\" is not used.");
|
"Label \"" ~ label.name ~ "\" is not used.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,9 @@ final class UnusedParameterCheck : UnusedStorageCheck
|
||||||
* Params:
|
* Params:
|
||||||
* fileName = the name of the file being analyzed
|
* fileName = the name of the file being analyzed
|
||||||
*/
|
*/
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests, "Parameter", "unused_parameter");
|
super(args, "Parameter", "unused_parameter");
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Parameter parameter)
|
override void visit(const Parameter parameter)
|
||||||
|
|
|
@ -41,9 +41,9 @@ public:
|
||||||
const(DSymbol)* noreturn_;
|
const(DSymbol)* noreturn_;
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests);
|
super(args);
|
||||||
void_ = sc.getSymbolsByName(internString("void"))[0];
|
void_ = sc.getSymbolsByName(internString("void"))[0];
|
||||||
auto symbols = sc.getSymbolsByName(internString("noreturn"));
|
auto symbols = sc.getSymbolsByName(internString("noreturn"));
|
||||||
if (symbols.length > 0)
|
if (symbols.length > 0)
|
||||||
|
|
|
@ -23,9 +23,9 @@ final class UnusedVariableCheck : UnusedStorageCheck
|
||||||
* Params:
|
* Params:
|
||||||
* fileName = the name of the file being analyzed
|
* fileName = the name of the file being analyzed
|
||||||
*/
|
*/
|
||||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, sc, skipTests, "Variable", "unused_variable");
|
super(args, "Variable", "unused_variable");
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const VariableDeclaration variableDeclaration)
|
override void visit(const VariableDeclaration variableDeclaration)
|
||||||
|
@ -125,6 +125,12 @@ final class UnusedVariableCheck : UnusedStorageCheck
|
||||||
__traits(isPOD);
|
__traits(isPOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void unitthreaded()
|
||||||
|
{
|
||||||
|
auto testVar = foo.sort!myComp;
|
||||||
|
genVar.should == testVar;
|
||||||
|
}
|
||||||
|
|
||||||
}c, sac);
|
}c, sac);
|
||||||
stderr.writeln("Unittest for UnusedVariableCheck passed.");
|
stderr.writeln("Unittest for UnusedVariableCheck passed.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,9 @@ final class UselessAssertCheck : BaseAnalyzer
|
||||||
mixin AnalyzerInfo!"useless_assert_check";
|
mixin AnalyzerInfo!"useless_assert_check";
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AssertExpression ae)
|
override void visit(const AssertExpression ae)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
module dscanner.analysis.useless_initializer;
|
module dscanner.analysis.useless_initializer;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dscanner.analysis.nolint;
|
||||||
import dscanner.utils : safeAccess;
|
import dscanner.utils : safeAccess;
|
||||||
import containers.dynamicarray;
|
import containers.dynamicarray;
|
||||||
import containers.hashmap;
|
import containers.hashmap;
|
||||||
|
@ -33,7 +34,7 @@ final class UselessInitializerChecker : BaseAnalyzer
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
enum key = "dscanner.useless-initializer";
|
enum string KEY = "dscanner.useless-initializer";
|
||||||
|
|
||||||
version(unittest)
|
version(unittest)
|
||||||
{
|
{
|
||||||
|
@ -55,9 +56,9 @@ private:
|
||||||
public:
|
public:
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
_inStruct.insert(false);
|
_inStruct.insert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +93,10 @@ public:
|
||||||
override void visit(const(Declaration) decl)
|
override void visit(const(Declaration) decl)
|
||||||
{
|
{
|
||||||
_inStruct.insert(decl.structDeclaration !is null);
|
_inStruct.insert(decl.structDeclaration !is null);
|
||||||
decl.accept(this);
|
|
||||||
|
with (noLint.push(NoLintFactory.fromDeclaration(decl)))
|
||||||
|
decl.accept(this);
|
||||||
|
|
||||||
if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor &&
|
if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor &&
|
||||||
((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) ||
|
((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) ||
|
||||||
!decl.constructor.parameters))
|
!decl.constructor.parameters))
|
||||||
|
@ -157,7 +161,7 @@ public:
|
||||||
{
|
{
|
||||||
void warn(const BaseNode range)
|
void warn(const BaseNode range)
|
||||||
{
|
{
|
||||||
addErrorMessage(range, key, msg);
|
addErrorMessage(range, KEY, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -165,7 +169,7 @@ public:
|
||||||
import std.format : format;
|
import std.format : format;
|
||||||
void warn(const BaseNode range)
|
void warn(const BaseNode range)
|
||||||
{
|
{
|
||||||
addErrorMessage(range, key, msg.format(declarator.name.text));
|
addErrorMessage(range, KEY, msg.format(declarator.name.text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,6 +365,45 @@ public:
|
||||||
NotKnown nk = NotKnown.init;
|
NotKnown nk = NotKnown.init;
|
||||||
}, sac);
|
}, sac);
|
||||||
|
|
||||||
|
// passes
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@("nolint(dscanner.useless-initializer)")
|
||||||
|
int a = 0;
|
||||||
|
int a = 0; /+
|
||||||
|
^ [warn]: X +/
|
||||||
|
|
||||||
|
@("nolint(dscanner.useless-initializer)")
|
||||||
|
int f() {
|
||||||
|
int a = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct nolint { string s; }
|
||||||
|
|
||||||
|
@nolint("dscanner.useless-initializer")
|
||||||
|
int a = 0;
|
||||||
|
int a = 0; /+
|
||||||
|
^ [warn]: X +/
|
||||||
|
|
||||||
|
@("nolint(other_check, dscanner.useless-initializer, another_one)")
|
||||||
|
int a = 0;
|
||||||
|
|
||||||
|
@nolint("other_check", "another_one", "dscanner.useless-initializer")
|
||||||
|
int a = 0;
|
||||||
|
|
||||||
|
}, sac);
|
||||||
|
|
||||||
|
// passes (disable check at module level)
|
||||||
|
assertAnalyzerWarnings(q{
|
||||||
|
@("nolint(dscanner.useless-initializer)")
|
||||||
|
module my_module;
|
||||||
|
|
||||||
|
int a = 0;
|
||||||
|
|
||||||
|
int f() {
|
||||||
|
int a = 0;
|
||||||
|
}
|
||||||
|
}, sac);
|
||||||
|
|
||||||
stderr.writeln("Unittest for UselessInitializerChecker passed.");
|
stderr.writeln("Unittest for UselessInitializerChecker passed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,9 +145,9 @@ private:
|
||||||
public:
|
public:
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(BaseAnalyzerArguments args)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const(ClassDeclaration) decl)
|
override void visit(const(ClassDeclaration) decl)
|
||||||
|
|
|
@ -10,8 +10,10 @@ import std.array;
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
|
|
||||||
// http://ethanschoonover.com/solarized
|
// http://ethanschoonover.com/solarized
|
||||||
void highlight(R)(ref R tokens, string fileName)
|
void highlight(R)(ref R tokens, string fileName, string themeName)
|
||||||
{
|
{
|
||||||
|
immutable(Theme)* theme = getTheme(themeName);
|
||||||
|
|
||||||
stdout.writeln(q"[
|
stdout.writeln(q"[
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -20,17 +22,19 @@ void highlight(R)(ref R tokens, string fileName)
|
||||||
stdout.writeln("<title>", fileName, "</title>");
|
stdout.writeln("<title>", fileName, "</title>");
|
||||||
stdout.writeln(q"[</head>
|
stdout.writeln(q"[</head>
|
||||||
<body>
|
<body>
|
||||||
<style type="text/css">
|
<style type="text/css">]");
|
||||||
html { background-color: #fdf6e3; color: #002b36; }
|
stdout.writefln("
|
||||||
.kwrd { color: #b58900; font-weight: bold; }
|
html { background-color: %s; color: %s; }
|
||||||
.com { color: #93a1a1; font-style: italic; }
|
.kwrd { color: %s; font-weight: bold; }
|
||||||
.num { color: #dc322f; font-weight: bold; }
|
.com { color: %s; font-style: italic; }
|
||||||
.str { color: #2aa198; font-style: italic; }
|
.num { color: %s; font-weight: bold; }
|
||||||
.op { color: #586e75; font-weight: bold; }
|
.str { color: %s; font-style: italic; }
|
||||||
.type { color: #268bd2; font-weight: bold; }
|
.op { color: %s; font-weight: bold; }
|
||||||
.cons { color: #859900; font-weight: bold; }
|
.type { color: %s; font-weight: bold; }
|
||||||
|
.cons { color: %s; font-weight: bold; }
|
||||||
</style>
|
</style>
|
||||||
<pre>]");
|
<pre>", theme.bg, theme.fg, theme.kwrd, theme.com, theme.num, theme.str,
|
||||||
|
theme.op, theme.type, theme.cons);
|
||||||
|
|
||||||
while (!tokens.empty)
|
while (!tokens.empty)
|
||||||
{
|
{
|
||||||
|
@ -76,3 +80,37 @@ void writeSpan(string cssClass, string value)
|
||||||
stdout.write(`<span class="`, cssClass, `">`, value.replace("&",
|
stdout.write(`<span class="`, cssClass, `">`, value.replace("&",
|
||||||
"&").replace("<", "<"), `</span>`);
|
"&").replace("<", "<"), `</span>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Theme
|
||||||
|
{
|
||||||
|
string bg;
|
||||||
|
string fg;
|
||||||
|
string kwrd;
|
||||||
|
string com;
|
||||||
|
string num;
|
||||||
|
string str;
|
||||||
|
string op;
|
||||||
|
string type;
|
||||||
|
string cons;
|
||||||
|
}
|
||||||
|
|
||||||
|
immutable(Theme)* getTheme(string themeName)
|
||||||
|
{
|
||||||
|
immutable Theme[string] themes = [
|
||||||
|
"solarized": Theme("#fdf6e3", "#002b36", "#b58900", "#93a1a1", "#dc322f", "#2aa198", "#586e75",
|
||||||
|
"#268bd2", "#859900"),
|
||||||
|
"solarized-dark": Theme("#002b36", "#fdf6e3", "#b58900", "#586e75", "#dc322f", "#2aa198",
|
||||||
|
"#93a1a1", "#268bd2", "#859900"),
|
||||||
|
"gruvbox": Theme("#fbf1c7", "#282828", "#b57614", "#a89984", "#9d0006", "#427b58",
|
||||||
|
"#504945", "#076678", "#79740e"),
|
||||||
|
"gruvbox-dark": Theme("#282828", "#fbf1c7", "#d79921", "#7c6f64",
|
||||||
|
"#cc241d", "#689d6a", "#a89984", "#458588", "#98971a")
|
||||||
|
];
|
||||||
|
|
||||||
|
immutable(Theme)* theme = themeName in themes;
|
||||||
|
// Default theme
|
||||||
|
if (theme is null)
|
||||||
|
theme = &themes["solarized"];
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
|
@ -5,20 +5,21 @@
|
||||||
|
|
||||||
module dscanner.main;
|
module dscanner.main;
|
||||||
|
|
||||||
import std.algorithm;
|
|
||||||
import std.array;
|
|
||||||
import std.conv;
|
|
||||||
import std.file;
|
|
||||||
import std.getopt;
|
|
||||||
import std.path;
|
|
||||||
import std.stdio;
|
|
||||||
import std.range;
|
|
||||||
import std.experimental.lexer;
|
|
||||||
import std.typecons : scoped;
|
|
||||||
import std.functional : toDelegate;
|
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
import dparse.parser;
|
import dparse.parser;
|
||||||
import dparse.rollback_allocator;
|
import dparse.rollback_allocator;
|
||||||
|
import std.algorithm;
|
||||||
|
import std.array;
|
||||||
|
import std.conv;
|
||||||
|
import std.experimental.lexer;
|
||||||
|
import std.file;
|
||||||
|
import std.functional : toDelegate;
|
||||||
|
import std.getopt;
|
||||||
|
import std.path;
|
||||||
|
import std.range;
|
||||||
|
import std.stdio;
|
||||||
|
import std.string : chomp, splitLines;
|
||||||
|
import std.typecons : scoped;
|
||||||
|
|
||||||
import dscanner.highlighter;
|
import dscanner.highlighter;
|
||||||
import dscanner.stats;
|
import dscanner.stats;
|
||||||
|
@ -64,23 +65,29 @@ else
|
||||||
bool report;
|
bool report;
|
||||||
bool skipTests;
|
bool skipTests;
|
||||||
bool applySingleFixes;
|
bool applySingleFixes;
|
||||||
|
string theme;
|
||||||
string resolveMessage;
|
string resolveMessage;
|
||||||
string reportFormat;
|
string reportFormat;
|
||||||
string reportFile;
|
string reportFile;
|
||||||
string symbolName;
|
string symbolName;
|
||||||
string configLocation;
|
string configLocation;
|
||||||
string[] importPaths;
|
string[] importPaths;
|
||||||
|
string[] excludePaths;
|
||||||
bool printVersion;
|
bool printVersion;
|
||||||
bool explore;
|
bool explore;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
string errorFormat;
|
string errorFormat;
|
||||||
|
|
||||||
|
if (args.length == 2 && args[1].startsWith("@"))
|
||||||
|
args = args[0] ~ readText(args[1][1 .. $]).chomp.splitLines;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// dfmt off
|
// dfmt off
|
||||||
getopt(args, std.getopt.config.caseSensitive,
|
getopt(args, std.getopt.config.caseSensitive,
|
||||||
"sloc|l", &sloc,
|
"sloc|l", &sloc,
|
||||||
"highlight", &highlight,
|
"highlight", &highlight,
|
||||||
|
"theme", &theme,
|
||||||
"ctags|c", &ctags,
|
"ctags|c", &ctags,
|
||||||
"help|h", &help,
|
"help|h", &help,
|
||||||
"etags|e", &etags,
|
"etags|e", &etags,
|
||||||
|
@ -102,6 +109,7 @@ else
|
||||||
"resolveMessage", &resolveMessage,
|
"resolveMessage", &resolveMessage,
|
||||||
"applySingle", &applySingleFixes,
|
"applySingle", &applySingleFixes,
|
||||||
"I", &importPaths,
|
"I", &importPaths,
|
||||||
|
"exclude", &excludePaths,
|
||||||
"version", &printVersion,
|
"version", &printVersion,
|
||||||
"muffinButton", &muffin,
|
"muffinButton", &muffin,
|
||||||
"explore", &explore,
|
"explore", &explore,
|
||||||
|
@ -191,6 +199,24 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto expandedArgs = () {
|
||||||
|
auto expanded = expandArgs(args);
|
||||||
|
if (excludePaths.length)
|
||||||
|
{
|
||||||
|
string[] newArgs = [expanded[0]];
|
||||||
|
foreach(arg; args[1 .. $])
|
||||||
|
{
|
||||||
|
if(!excludePaths.map!(p => arg.isSubpathOf(p))
|
||||||
|
.fold!((a, b) => a || b))
|
||||||
|
newArgs ~= arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArgs;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return expanded;
|
||||||
|
}();
|
||||||
|
|
||||||
if (!errorFormat.length)
|
if (!errorFormat.length)
|
||||||
errorFormat = defaultErrorFormat;
|
errorFormat = defaultErrorFormat;
|
||||||
else if (auto errorFormatSuppl = errorFormat in errorFormatMap)
|
else if (auto errorFormatSuppl = errorFormat in errorFormatMap)
|
||||||
|
@ -202,8 +228,7 @@ else
|
||||||
.replace("\\n", "\n")
|
.replace("\\n", "\n")
|
||||||
.replace("\\t", "\t");
|
.replace("\\t", "\t");
|
||||||
|
|
||||||
const(string[]) absImportPaths = importPaths.map!(a => a.absolutePath()
|
const(string[]) absImportPaths = importPaths.map!absoluteNormalizedPath.array;
|
||||||
.buildNormalizedPath()).array();
|
|
||||||
|
|
||||||
ModuleCache moduleCache;
|
ModuleCache moduleCache;
|
||||||
|
|
||||||
|
@ -253,7 +278,7 @@ else
|
||||||
if (highlight)
|
if (highlight)
|
||||||
{
|
{
|
||||||
auto tokens = byToken(bytes, config, &cache);
|
auto tokens = byToken(bytes, config, &cache);
|
||||||
dscanner.highlighter.highlight(tokens, args.length == 1 ? "stdin" : args[1]);
|
dscanner.highlighter.highlight(tokens, args.length == 1 ? "stdin" : args[1], theme);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (tokenDump)
|
else if (tokenDump)
|
||||||
|
@ -278,15 +303,15 @@ else
|
||||||
}
|
}
|
||||||
else if (symbolName !is null)
|
else if (symbolName !is null)
|
||||||
{
|
{
|
||||||
stdout.findDeclarationOf(symbolName, expandArgs(args));
|
stdout.findDeclarationOf(symbolName, expandedArgs);
|
||||||
}
|
}
|
||||||
else if (ctags)
|
else if (ctags)
|
||||||
{
|
{
|
||||||
stdout.printCtags(expandArgs(args));
|
stdout.printCtags(expandedArgs);
|
||||||
}
|
}
|
||||||
else if (etags || etagsAll)
|
else if (etags || etagsAll)
|
||||||
{
|
{
|
||||||
stdout.printEtags(etagsAll, expandArgs(args));
|
stdout.printEtags(etagsAll, expandedArgs);
|
||||||
}
|
}
|
||||||
else if (styleCheck || autofix || resolveMessage.length)
|
else if (styleCheck || autofix || resolveMessage.length)
|
||||||
{
|
{
|
||||||
|
@ -299,7 +324,7 @@ else
|
||||||
|
|
||||||
if (autofix)
|
if (autofix)
|
||||||
{
|
{
|
||||||
return .autofix(expandArgs(args), config, errorFormat, cache, moduleCache, applySingleFixes) ? 1 : 0;
|
return .autofix(expandedArgs, config, errorFormat, cache, moduleCache, applySingleFixes) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else if (resolveMessage.length)
|
else if (resolveMessage.length)
|
||||||
{
|
{
|
||||||
|
@ -315,19 +340,19 @@ else
|
||||||
goto case;
|
goto case;
|
||||||
case "":
|
case "":
|
||||||
case "dscanner":
|
case "dscanner":
|
||||||
generateReport(expandArgs(args), config, cache, moduleCache, reportFile);
|
generateReport(expandedArgs, config, cache, moduleCache, reportFile);
|
||||||
break;
|
break;
|
||||||
case "sonarQubeGenericIssueData":
|
case "sonarQubeGenericIssueData":
|
||||||
generateSonarQubeGenericIssueDataReport(expandArgs(args), config, cache, moduleCache, reportFile);
|
generateSonarQubeGenericIssueDataReport(expandedArgs, config, cache, moduleCache, reportFile);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return analyze(expandArgs(args), config, errorFormat, cache, moduleCache, true) ? 1 : 0;
|
return analyze(expandedArgs, config, errorFormat, cache, moduleCache, true) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else if (syntaxCheck)
|
else if (syntaxCheck)
|
||||||
{
|
{
|
||||||
return .syntaxCheck(usingStdin ? ["stdin"] : expandArgs(args), errorFormat, cache, moduleCache) ? 1 : 0;
|
return .syntaxCheck(usingStdin ? ["stdin"] : expandedArgs, errorFormat, cache, moduleCache) ? 1 : 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -346,7 +371,7 @@ else
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ulong count;
|
ulong count;
|
||||||
foreach (f; expandArgs(args))
|
foreach (f; expandedArgs)
|
||||||
{
|
{
|
||||||
|
|
||||||
LexerConfig config;
|
LexerConfig config;
|
||||||
|
@ -393,7 +418,7 @@ else
|
||||||
|
|
||||||
void printHelp(string programName)
|
void printHelp(string programName)
|
||||||
{
|
{
|
||||||
stderr.writefln(`
|
stdout.writefln(`
|
||||||
Usage: %1$s <options>
|
Usage: %1$s <options>
|
||||||
|
|
||||||
Human-readable output:
|
Human-readable output:
|
||||||
|
@ -444,6 +469,9 @@ Options:
|
||||||
modules. This option can be passed multiple times to specify multiple
|
modules. This option can be passed multiple times to specify multiple
|
||||||
directories.
|
directories.
|
||||||
|
|
||||||
|
--exclude <file | directory>..., <file | directory>
|
||||||
|
Specify files or directories that will be ignored by D-Scanner.
|
||||||
|
|
||||||
--syntaxCheck <file>, -s <file>
|
--syntaxCheck <file>, -s <file>
|
||||||
Lexes and parses sourceFile, printing the line and column number of
|
Lexes and parses sourceFile, printing the line and column number of
|
||||||
any syntax errors to stdout. One error or warning is printed per line,
|
any syntax errors to stdout. One error or warning is printed per line,
|
||||||
|
@ -546,6 +574,9 @@ private enum CONFIG_FILE_NAME = "dscanner.ini";
|
||||||
version (linux) version = useXDG;
|
version (linux) version = useXDG;
|
||||||
version (BSD) version = useXDG;
|
version (BSD) version = useXDG;
|
||||||
version (FreeBSD) version = useXDG;
|
version (FreeBSD) version = useXDG;
|
||||||
|
version (OpenBSD) version = useXDG;
|
||||||
|
version (NetBSD) version = useXDG;
|
||||||
|
version (DragonflyBSD) version = useXDG;
|
||||||
version (OSX) version = useXDG;
|
version (OSX) version = useXDG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -55,6 +55,9 @@ class DScannerJsonReporter
|
||||||
|
|
||||||
private static JSONValue toJson(Issue issue)
|
private static JSONValue toJson(Issue issue)
|
||||||
{
|
{
|
||||||
|
import std.sumtype : match;
|
||||||
|
import dscanner.analysis.base : AutoFix;
|
||||||
|
|
||||||
// dfmt off
|
// dfmt off
|
||||||
JSONValue js = JSONValue([
|
JSONValue js = JSONValue([
|
||||||
"key": JSONValue(issue.message.key),
|
"key": JSONValue(issue.message.key),
|
||||||
|
@ -80,6 +83,27 @@ class DScannerJsonReporter
|
||||||
"message": JSONValue(a.message),
|
"message": JSONValue(a.message),
|
||||||
])
|
])
|
||||||
).array
|
).array
|
||||||
|
),
|
||||||
|
"autofixes": JSONValue(
|
||||||
|
issue.message.autofixes.map!(a =>
|
||||||
|
JSONValue([
|
||||||
|
"name": JSONValue(a.name),
|
||||||
|
"replacements": a.replacements.match!(
|
||||||
|
(const AutoFix.CodeReplacement[] replacements) => JSONValue(
|
||||||
|
replacements.map!(r => JSONValue([
|
||||||
|
"range": JSONValue([
|
||||||
|
JSONValue(r.range[0]),
|
||||||
|
JSONValue(r.range[1])
|
||||||
|
]),
|
||||||
|
"newText": JSONValue(r.newText)
|
||||||
|
])).array
|
||||||
|
),
|
||||||
|
(const AutoFix.ResolveContext _) => JSONValue(
|
||||||
|
"resolvable"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
).array
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
// dfmt on
|
// dfmt on
|
||||||
|
|
|
@ -6,6 +6,7 @@ import std.conv : to;
|
||||||
import std.encoding : BOM, BOMSeq, EncodingException, getBOM;
|
import std.encoding : BOM, BOMSeq, EncodingException, getBOM;
|
||||||
import std.format : format;
|
import std.format : format;
|
||||||
import std.file : exists, read;
|
import std.file : exists, read;
|
||||||
|
import std.path: isValidPath;
|
||||||
|
|
||||||
private void processBOM(ref ubyte[] sourceCode, string fname)
|
private void processBOM(ref ubyte[] sourceCode, string fname)
|
||||||
{
|
{
|
||||||
|
@ -128,12 +129,63 @@ string[] expandArgs(string[] args)
|
||||||
return rVal;
|
return rVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package string absoluteNormalizedPath(in string path)
|
||||||
|
{
|
||||||
|
import std.path: absolutePath, buildNormalizedPath;
|
||||||
|
|
||||||
|
return path.absolutePath().buildNormalizedPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool areSamePath(in string path1, in string path2)
|
||||||
|
in(path1.isValidPath && path2.isValidPath)
|
||||||
|
{
|
||||||
|
return path1.absoluteNormalizedPath() == path2.absoluteNormalizedPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
assert(areSamePath("/abc/efg", "/abc/efg"));
|
||||||
|
assert(areSamePath("/abc/../abc/efg", "/abc/efg"));
|
||||||
|
assert(!areSamePath("/abc/../abc/../efg", "/abc/efg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
package bool isSubpathOf(in string potentialSubPath, in string base)
|
||||||
|
in(base.isValidPath && potentialSubPath.isValidPath)
|
||||||
|
{
|
||||||
|
import std.path: isValidPath, relativePath;
|
||||||
|
import std.algorithm: canFind;
|
||||||
|
|
||||||
|
if(areSamePath(base, potentialSubPath))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const relative = relativePath(
|
||||||
|
potentialSubPath.absoluteNormalizedPath(),
|
||||||
|
base.absoluteNormalizedPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
// No '..' in the relative paths means that potentialSubPath
|
||||||
|
// is actually a descendant of base
|
||||||
|
return !relative.canFind("..");
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
const base = "/abc/efg";
|
||||||
|
assert("/abc/efg/".isSubpathOf(base));
|
||||||
|
assert("/abc/efg/hij/".isSubpathOf(base));
|
||||||
|
assert("/abc/efg/hij/../kel".isSubpathOf(base));
|
||||||
|
assert(!"/abc/kel".isSubpathOf(base));
|
||||||
|
assert(!"/abc/efg/../kel".isSubpathOf(base));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to build access chains of class members as done with the $(D ?.) operator
|
* Allows to build access chains of class members as done with the $(D ?.) operator
|
||||||
* in other languages. In the chain, any $(D null) member that is a class instance
|
* in other languages. In the chain, any $(D null) member that is a class instance
|
||||||
* or that returns one, has for effect to shortcut the complete evaluation.
|
* or that returns one, has for effect to shortcut the complete evaluation.
|
||||||
*
|
*
|
||||||
* This function is copied from https://github.com/BBasile/iz to avoid a new submodule.
|
* This function is copied from
|
||||||
|
* https://gitlab.com/basile.b/iz/-/blob/18f5c1e78a89edae9f7bd9c2d8e7e0c152f56696/import/iz/sugar.d#L1543
|
||||||
|
* to avoid adding additional dependencies.
|
||||||
* Any change made to this copy should also be applied to the origin.
|
* Any change made to this copy should also be applied to the origin.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
; Configure which static analysis checks are enabled
|
; Configure which static analysis checks are enabled
|
||||||
[analysis.config.StaticAnalysisConfig]
|
[analysis.config.StaticAnalysisConfig]
|
||||||
; Check variable, class, struct, interface, union, and function names against the Phobos style guide
|
; Check variable, class, struct, interface, union, and function names against the Phobos style guide
|
||||||
style_check="disabled"
|
style_check="enabled"
|
||||||
; Check for array literals that cause unnecessary allocation
|
; Check for array literals that cause unnecessary allocation
|
||||||
enum_array_literal_check="enabled"
|
enum_array_literal_check="enabled"
|
||||||
; Check for poor exception handling practices
|
; Check for poor exception handling practices
|
||||||
|
|
72
tests/it.sh
72
tests/it.sh
|
@ -2,15 +2,81 @@
|
||||||
|
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
|
function section {
|
||||||
|
e=$'\e'
|
||||||
|
if [ ! -z "${GITHUB_ACTION:-}" ]; then
|
||||||
|
echo "::endgroup::"
|
||||||
|
echo "::group::$@"
|
||||||
|
else
|
||||||
|
echo "$e[1m$@$e[m"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function error {
|
||||||
|
echo $'\e[31;1mTests have failed.\e[m'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
if [ ! -z "${GITHUB_ACTION:-}" ]; then
|
||||||
|
echo "::endgroup::"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
DSCANNER_DIR="$(dirname -- $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ))"
|
DSCANNER_DIR="$(dirname -- $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ))"
|
||||||
dub build --root="$DSCANNER_DIR"
|
|
||||||
|
if [ ! -z "${GITHUB_ACTION:-}" ]; then
|
||||||
|
echo "::group::Building d-scanner"
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap error ERR
|
||||||
|
|
||||||
|
if [ -z "${CI:-}" ]; then
|
||||||
|
dub build --root="$DSCANNER_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
cd "$DSCANNER_DIR/tests"
|
cd "$DSCANNER_DIR/tests"
|
||||||
|
|
||||||
# IDE APIs
|
# IDE APIs
|
||||||
# --------
|
# --------
|
||||||
# checking that reporting format stays consistent or only gets extended
|
# checking that reporting format stays consistent or only gets extended
|
||||||
diff <(jq -S . <(../bin/dscanner --report it/source_autofix.d)) <(jq -S . it/source_autofix.report.json)
|
diff <(../bin/dscanner --report it/autofix_ide/source_autofix.d | jq -S .) <(jq -S . it/autofix_ide/source_autofix.report.json)
|
||||||
diff <(jq -S . <(../bin/dscanner --resolveMessage b16 it/source_autofix.d)) <(jq -S . it/source_autofix.autofix.json)
|
diff <(../bin/dscanner --resolveMessage b16 it/autofix_ide/source_autofix.d | jq -S .) <(jq -S . it/autofix_ide/source_autofix.autofix.json)
|
||||||
|
|
||||||
|
# CLI tests
|
||||||
|
# ---------
|
||||||
|
# check that `dscanner fix` works as expected
|
||||||
|
section '1. test no changes if EOFing'
|
||||||
|
cp -v it/autofix_cli/source.d it/autofix_cli/test.d
|
||||||
|
printf "" | ../bin/dscanner fix it/autofix_cli/test.d
|
||||||
|
diff it/autofix_cli/test.d it/autofix_cli/source.d
|
||||||
|
section '2. test no changes for simple enter pressing'
|
||||||
|
cp -v it/autofix_cli/source.d it/autofix_cli/test.d
|
||||||
|
printf "\n" | ../bin/dscanner fix it/autofix_cli/test.d
|
||||||
|
diff it/autofix_cli/test.d it/autofix_cli/source.d
|
||||||
|
section '2.1. test no changes entering 0'
|
||||||
|
cp -v it/autofix_cli/source.d it/autofix_cli/test.d
|
||||||
|
printf "0\n" | ../bin/dscanner fix it/autofix_cli/test.d
|
||||||
|
diff it/autofix_cli/test.d it/autofix_cli/source.d
|
||||||
|
section '3. test change applies automatically with --applySingle'
|
||||||
|
cp -v it/autofix_cli/source.d it/autofix_cli/test.d
|
||||||
|
../bin/dscanner fix --applySingle it/autofix_cli/test.d | grep -F 'Writing changes to it/autofix_cli/test.d'
|
||||||
|
diff it/autofix_cli/test.d it/autofix_cli/fixed.d
|
||||||
|
section '4. test change apply when entering "1"'
|
||||||
|
cp -v it/autofix_cli/source.d it/autofix_cli/test.d
|
||||||
|
printf "1\n" | ../bin/dscanner fix it/autofix_cli/test.d | grep -F 'Writing changes to it/autofix_cli/test.d'
|
||||||
|
diff it/autofix_cli/test.d it/autofix_cli/fixed.d
|
||||||
|
section '5. test invalid selection reasks what to apply'
|
||||||
|
cp -v it/autofix_cli/source.d it/autofix_cli/test.d
|
||||||
|
printf "2\n-1\n1000\na\n1\n" | ../bin/dscanner fix it/autofix_cli/test.d | grep -F 'Writing changes to it/autofix_cli/test.d'
|
||||||
|
diff it/autofix_cli/test.d it/autofix_cli/fixed.d
|
||||||
|
|
||||||
|
# check that `dscanner @myargs.rst` reads arguments from file
|
||||||
|
section "Test @myargs.rst"
|
||||||
|
echo "-f" > "myargs.rst"
|
||||||
|
echo "github" >> "myargs.rst"
|
||||||
|
echo "lint" >> "myargs.rst"
|
||||||
|
echo "it/singleissue.d" >> "myargs.rst"
|
||||||
|
diff it/singleissue_github.txt <(../bin/dscanner "@myargs.rst")
|
||||||
|
rm "myargs.rst"
|
||||||
|
|
1
tests/it/autofix_cli/.gitignore
vendored
Normal file
1
tests/it/autofix_cli/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
test.d
|
3
tests/it/autofix_cli/fixed.d
Normal file
3
tests/it/autofix_cli/fixed.d
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
}
|
3
tests/it/autofix_cli/source.d
Normal file
3
tests/it/autofix_cli/source.d
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
auto main()
|
||||||
|
{
|
||||||
|
}
|
12
tests/it/autofix_ide/source_autofix.d
Normal file
12
tests/it/autofix_ide/source_autofix.d
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
struct S
|
||||||
|
{
|
||||||
|
int myProp() @property
|
||||||
|
{
|
||||||
|
static if (a)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else if (b)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
tests/it/autofix_ide/source_autofix.report.json
Normal file
96
tests/it/autofix_ide/source_autofix.report.json
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
{
|
||||||
|
"classCount": 0,
|
||||||
|
"functionCount": 1,
|
||||||
|
"interfaceCount": 0,
|
||||||
|
"issues": [
|
||||||
|
{
|
||||||
|
"column": 6,
|
||||||
|
"endColumn": 12,
|
||||||
|
"endIndex": 22,
|
||||||
|
"endLine": 3,
|
||||||
|
"fileName": "it/autofix_ide/source_autofix.d",
|
||||||
|
"index": 16,
|
||||||
|
"key": "dscanner.confusing.function_attributes",
|
||||||
|
"line": 3,
|
||||||
|
"message": "Zero-parameter '@property' function should be marked 'const', 'inout', or 'immutable'.",
|
||||||
|
"name": "function_attribute_check",
|
||||||
|
"supplemental": [],
|
||||||
|
"type": "warn",
|
||||||
|
"autofixes": [
|
||||||
|
{
|
||||||
|
"name": "Mark function `const`",
|
||||||
|
"replacements": [
|
||||||
|
{
|
||||||
|
"newText": " const",
|
||||||
|
"range": [
|
||||||
|
24,
|
||||||
|
24
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mark function `inout`",
|
||||||
|
"replacements": [
|
||||||
|
{
|
||||||
|
"newText": " inout",
|
||||||
|
"range": [
|
||||||
|
24,
|
||||||
|
24
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mark function `immutable`",
|
||||||
|
"replacements": [
|
||||||
|
{
|
||||||
|
"newText": " immutable",
|
||||||
|
"range": [
|
||||||
|
24,
|
||||||
|
24
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autofixes": [
|
||||||
|
{
|
||||||
|
"name": "Insert `static`",
|
||||||
|
"replacements": [
|
||||||
|
{
|
||||||
|
"newText": "static ",
|
||||||
|
"range": [
|
||||||
|
69,
|
||||||
|
69
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wrap '{}' block around 'if'",
|
||||||
|
"replacements": "resolvable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"column": 3,
|
||||||
|
"endColumn": 10,
|
||||||
|
"endIndex": 71,
|
||||||
|
"endLine": 8,
|
||||||
|
"fileName": "it/autofix_ide/source_autofix.d",
|
||||||
|
"index": 64,
|
||||||
|
"key": "dscanner.suspicious.static_if_else",
|
||||||
|
"line": 8,
|
||||||
|
"message": "Mismatched static if. Use 'else static if' here.",
|
||||||
|
"name": "static_if_else_check",
|
||||||
|
"supplemental": [],
|
||||||
|
"type": "warn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lineOfCodeCount": 3,
|
||||||
|
"statementCount": 4,
|
||||||
|
"structCount": 1,
|
||||||
|
"templateCount": 0,
|
||||||
|
"undocumentedPublicSymbols": 0
|
||||||
|
}
|
1
tests/it/singleissue.d
Normal file
1
tests/it/singleissue.d
Normal file
|
@ -0,0 +1 @@
|
||||||
|
int NonMatchingName;
|
1
tests/it/singleissue_github.txt
Normal file
1
tests/it/singleissue_github.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
::warning file=it/singleissue.d,line=1,endLine=1,col=5,endColumn=20,title=Warning (style_check)::Variable name 'NonMatchingName' does not match style guidelines.
|
|
@ -1,6 +0,0 @@
|
||||||
struct S
|
|
||||||
{
|
|
||||||
int myProp() @property
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"classCount": 0,
|
|
||||||
"functionCount": 1,
|
|
||||||
"interfaceCount": 0,
|
|
||||||
"issues": [
|
|
||||||
{
|
|
||||||
"column": 6,
|
|
||||||
"endColumn": 12,
|
|
||||||
"endIndex": 22,
|
|
||||||
"endLine": 3,
|
|
||||||
"fileName": "it\/source_autofix.d",
|
|
||||||
"index": 16,
|
|
||||||
"key": "dscanner.confusing.function_attributes",
|
|
||||||
"line": 3,
|
|
||||||
"message": "Zero-parameter '@property' function should be marked 'const', 'inout', or 'immutable'.",
|
|
||||||
"name": "function_attribute_check",
|
|
||||||
"supplemental": [],
|
|
||||||
"type": "warn"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lineOfCodeCount": 0,
|
|
||||||
"statementCount": 0,
|
|
||||||
"structCount": 1,
|
|
||||||
"templateCount": 0,
|
|
||||||
"undocumentedPublicSymbols": 0
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue