Merge branch 'master' into phobos
This commit is contained in:
commit
1b88b41f6b
23
.travis.yml
23
.travis.yml
|
@ -1,8 +1,6 @@
|
|||
sudo: false
|
||||
language: d
|
||||
d:
|
||||
- dmd-nightly
|
||||
- dmd-beta
|
||||
- dmd
|
||||
- ldc-beta
|
||||
- ldc
|
||||
|
@ -43,6 +41,27 @@ jobs:
|
|||
on:
|
||||
repo: dlang-community/D-Scanner
|
||||
tags: true
|
||||
- stage: GitHub Release
|
||||
#if: tag IS present
|
||||
d: dmd
|
||||
os: linux
|
||||
language: generic
|
||||
sudo: yes
|
||||
script: echo "Deploying to GitHub releases ..." && ./release-windows.sh
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- p7zip-full
|
||||
- wine
|
||||
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
|
||||
stages:
|
||||
- name: test
|
||||
if: type = pull_request or (type = push and branch = master)
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
platform: x64
|
||||
environment:
|
||||
matrix:
|
||||
#- DC: dmd
|
||||
#DVersion: beta
|
||||
#arch: x64
|
||||
#- DC: dmd
|
||||
#DVersion: beta
|
||||
#arch: x86
|
||||
- DC: dmd
|
||||
DVersion: stable
|
||||
arch: x64
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 6c5504cc80b75192b24cebe93209521c03f806d8
|
||||
Subproject commit c261fa119072ce788ef81b8d8fee9a2adddca5d1
|
2
dsymbol
2
dsymbol
|
@ -1 +1 @@
|
|||
Subproject commit 239b137b280c06864b73fcc1d00b75e06568d4c2
|
||||
Subproject commit e018446b028b92c34398032e698c05c0919630ee
|
18
dub.json
18
dub.json
|
@ -12,12 +12,18 @@
|
|||
"StdLoggerDisableWarning"
|
||||
],
|
||||
"dependencies" : {
|
||||
"libdparse": "~>0.8.0",
|
||||
"dsymbol" : "~>0.3.0",
|
||||
"inifiled" : "~>1.3.0",
|
||||
"emsi_containers" : "~>0.6.0",
|
||||
"libdparse" : "~>0.8.6",
|
||||
"dsymbol" : "~>0.3.7",
|
||||
"inifiled" : "~>1.3.1",
|
||||
"emsi_containers" : "~>0.8.0-alpha.7",
|
||||
"libddoc" : "~>0.3.0-beta.1",
|
||||
"stdx-allocator" : "~>2.77.0"
|
||||
"stdx-allocator" : "~>2.77.2"
|
||||
},
|
||||
"targetPath" : "bin"
|
||||
"targetPath" : "bin",
|
||||
"stringImportPaths" : [
|
||||
"bin"
|
||||
],
|
||||
"preGenerateCommands" : [
|
||||
"rdmd --eval=\"auto dir=environment.get(\\\"DUB_PACKAGE_DIR\\\"); dir.buildPath(\\\"bin\\\").mkdirRecurse; auto gitVer = (\\\"git -C \\\"~dir~\\\" describe --tags\\\").executeShell; (gitVer.status == 0 ? gitVer.output : dir.dirName.baseName.findSplitAfter(environment.get(\\\"DUB_ROOT_PACKAGE\\\")~\\\"-\\\")[1]).ifThrown(\\\"0.0.0\\\").toFile(dir.buildPath(\\\"bin\\\", \\\"dubhash.txt\\\"));\""
|
||||
]
|
||||
}
|
||||
|
|
2
inifiled
2
inifiled
|
@ -1 +1 @@
|
|||
Subproject commit 9d7333ec17f116a05712a24df139ff2f212a9867
|
||||
Subproject commit cecaff8037a60db2a51c9bded4802c87d938a44e
|
|
@ -1 +1 @@
|
|||
Subproject commit 970efe34e66fc7b3cb93a6ec59984099908070c5
|
||||
Subproject commit cf102ff8e848fb18d2ce7056ae61dafb5333012d
|
2
makefile
2
makefile
|
@ -36,7 +36,7 @@ ldc: ldcbuild
|
|||
gdc: gdcbuild
|
||||
|
||||
githash:
|
||||
git log -1 --format="%H" > githash.txt
|
||||
git describe --tags > githash.txt
|
||||
|
||||
debug:
|
||||
${DC} -fPIC -w -g -J. -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build the Windows binaries under Linux (requires wine)
|
||||
set -eux -o pipefail
|
||||
VERSION=$(git describe --abbrev=0 --tags)
|
||||
OS=windows
|
||||
ARCH_SUFFIX="x86"
|
||||
|
||||
# Allow the script to be run from anywhere
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd $DIR
|
||||
|
||||
# Step 1: download the DMD binaries
|
||||
if [ ! -d dmd2 ] ; then
|
||||
wget http://downloads.dlang.org/releases/2.x/2.079.0/dmd.2.079.0.windows.7z
|
||||
7z x dmd.2.079.0.windows.7z > /dev/null
|
||||
fi
|
||||
|
||||
# Step 2: Run DMD via wineconsole
|
||||
archiveName="dscanner-$VERSION-$OS-$ARCH_SUFFIX.zip"
|
||||
echo "Building $archiveName"
|
||||
mkdir -p bin
|
||||
DC="$DIR/dmd2/windows/bin/dmd.exe" wine cmd /C build.bat
|
||||
|
||||
cd bin
|
||||
zip "$archiveName" dscanner.exe
|
|
@ -12,7 +12,7 @@ import dscanner.analysis.base;
|
|||
/**
|
||||
* Checks for uses of the old alias syntax.
|
||||
*/
|
||||
class AliasSyntaxCheck : BaseAnalyzer
|
||||
final class AliasSyntaxCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ if (param < 0)
|
|||
}
|
||||
------------
|
||||
*/
|
||||
class AllManCheck : BaseAnalyzer
|
||||
final class AllManCheck : BaseAnalyzer
|
||||
{
|
||||
///
|
||||
this(string fileName, const(Token)[] tokens, bool skipTests = false)
|
||||
|
@ -57,7 +57,7 @@ class AllManCheck : BaseAnalyzer
|
|||
addErrorMessage(tokens[i].line, tokens[i].column, KEY, MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum string KEY = "dscanner.style.allman";
|
||||
enum string MESSAGE = "Braces should be on their own line";
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_ : Scope;
|
|||
* Checks for confusing asm expressions.
|
||||
* See_also: $(LINK https://issues.dlang.org/show_bug.cgi?id=9738)
|
||||
*/
|
||||
class AsmStyleCheck : BaseAnalyzer
|
||||
final class AsmStyleCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import std.algorithm;
|
|||
/**
|
||||
* Check that all asserts have an explanatory message.
|
||||
*/
|
||||
class AssertWithoutMessageCheck : BaseAnalyzer
|
||||
final class AssertWithoutMessageCheck : BaseAnalyzer
|
||||
{
|
||||
enum string KEY = "dscanner.style.assert_without_msg";
|
||||
enum string MESSAGE = "An assert should have an explanatory message";
|
||||
|
|
|
@ -30,7 +30,7 @@ private:
|
|||
|
||||
bool[] _returns;
|
||||
size_t _mixinDepth;
|
||||
string[] _literalWithReturn;
|
||||
string[] _literalWithReturn;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -98,59 +98,59 @@ public:
|
|||
import std.algorithm.searching : canFind;
|
||||
|
||||
if (_returns.length && _mixinDepth)
|
||||
{
|
||||
if (findReturnInLiteral(exp.primary.text))
|
||||
{
|
||||
if (findReturnInLiteral(exp.primary.text))
|
||||
_returns[$-1] = true;
|
||||
else if (exp.identifierOrTemplateInstance &&
|
||||
_literalWithReturn.canFind(exp.identifierOrTemplateInstance.identifier.text))
|
||||
_returns[$-1] = true;
|
||||
}
|
||||
else if (exp.identifierOrTemplateInstance &&
|
||||
_literalWithReturn.canFind(exp.identifierOrTemplateInstance.identifier.text))
|
||||
_returns[$-1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool findReturnInLiteral(const(string) value)
|
||||
{
|
||||
import std.algorithm.searching : find;
|
||||
import std.range : empty;
|
||||
bool findReturnInLiteral(const(string) value)
|
||||
{
|
||||
import std.algorithm.searching : find;
|
||||
import std.range : empty;
|
||||
|
||||
return value == "return" || !value.find("return ").empty;
|
||||
}
|
||||
return value == "return" || !value.find("return ").empty;
|
||||
}
|
||||
|
||||
bool stringliteralHasReturn(const(NonVoidInitializer) nvi)
|
||||
{
|
||||
bool result;
|
||||
if (!nvi.assignExpression || (cast(UnaryExpression) nvi.assignExpression) is null)
|
||||
return result;
|
||||
bool stringliteralHasReturn(const(NonVoidInitializer) nvi)
|
||||
{
|
||||
bool result;
|
||||
if (!nvi.assignExpression || (cast(UnaryExpression) nvi.assignExpression) is null)
|
||||
return result;
|
||||
|
||||
const(UnaryExpression) u = cast(UnaryExpression) nvi.assignExpression;
|
||||
if (u.primaryExpression &&
|
||||
u.primaryExpression.primary.type.isStringLiteral &&
|
||||
findReturnInLiteral(u.primaryExpression.primary.text))
|
||||
result = true;
|
||||
const(UnaryExpression) u = cast(UnaryExpression) nvi.assignExpression;
|
||||
if (u.primaryExpression &&
|
||||
u.primaryExpression.primary.type.isStringLiteral &&
|
||||
findReturnInLiteral(u.primaryExpression.primary.text))
|
||||
result = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
override void visit(const(AutoDeclaration) decl)
|
||||
{
|
||||
decl.accept(this);
|
||||
override void visit(const(AutoDeclaration) decl)
|
||||
{
|
||||
decl.accept(this);
|
||||
|
||||
foreach(const(AutoDeclarationPart) p; decl.parts)
|
||||
if (p.initializer &&
|
||||
p.initializer.nonVoidInitializer &&
|
||||
stringliteralHasReturn(p.initializer.nonVoidInitializer))
|
||||
_literalWithReturn ~= p.identifier.text.idup;
|
||||
}
|
||||
foreach(const(AutoDeclarationPart) p; decl.parts)
|
||||
if (p.initializer &&
|
||||
p.initializer.nonVoidInitializer &&
|
||||
stringliteralHasReturn(p.initializer.nonVoidInitializer))
|
||||
_literalWithReturn ~= p.identifier.text.idup;
|
||||
}
|
||||
|
||||
override void visit(const(VariableDeclaration) decl)
|
||||
{
|
||||
decl.accept(this);
|
||||
override void visit(const(VariableDeclaration) decl)
|
||||
{
|
||||
decl.accept(this);
|
||||
|
||||
foreach(const(Declarator) d; decl.declarators)
|
||||
if (d.initializer &&
|
||||
d.initializer.nonVoidInitializer &&
|
||||
stringliteralHasReturn(d.initializer.nonVoidInitializer))
|
||||
_literalWithReturn ~= d.name.text.idup;
|
||||
}
|
||||
foreach(const(Declarator) d; decl.declarators)
|
||||
if (d.initializer &&
|
||||
d.initializer.nonVoidInitializer &&
|
||||
stringliteralHasReturn(d.initializer.nonVoidInitializer))
|
||||
_literalWithReturn ~= d.name.text.idup;
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
|
@ -202,24 +202,24 @@ unittest
|
|||
AutoFunctionChecker.MESSAGE,
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
auto doStuff(){} // [warn]: %s
|
||||
extern(C) auto doStuff();
|
||||
}c.format(
|
||||
AutoFunctionChecker.MESSAGE,
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
auto doStuff(){} // [warn]: %s
|
||||
extern(C) auto doStuff();
|
||||
}c.format(
|
||||
AutoFunctionChecker.MESSAGE,
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
auto doStuff(){} // [warn]: %s
|
||||
@disable auto doStuff();
|
||||
}c.format(
|
||||
AutoFunctionChecker.MESSAGE,
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
auto doStuff(){} // [warn]: %s
|
||||
@disable auto doStuff();
|
||||
}c.format(
|
||||
AutoFunctionChecker.MESSAGE,
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
enum _genSave = "return true;";
|
||||
auto doStuff(){ mixin(_genSave);}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
enum _genSave = "return true;";
|
||||
auto doStuff(){ mixin(_genSave);}
|
||||
}, sac);
|
||||
|
||||
stderr.writeln("Unittest for AutoFunctionChecker passed.");
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import dscanner.analysis.base;
|
|||
/**
|
||||
* Checks for assignment to auto-ref function parameters.
|
||||
*/
|
||||
class AutoRefAssignmentCheck : BaseAnalyzer
|
||||
final class AutoRefAssignmentCheck : BaseAnalyzer
|
||||
{
|
||||
///
|
||||
this(string fileName, bool skipTests = false)
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
{
|
||||
this.sc = sc;
|
||||
this.fileName = fileName;
|
||||
this.skipTests = skipTests;
|
||||
this.skipTests = skipTests;
|
||||
_messages = new MessageSet;
|
||||
}
|
||||
|
||||
|
@ -40,24 +40,24 @@ public:
|
|||
return _messages[].array;
|
||||
}
|
||||
|
||||
alias visit = ASTVisitor.visit;
|
||||
alias visit = ASTVisitor.visit;
|
||||
|
||||
/**
|
||||
* Visits a unittest.
|
||||
*
|
||||
* When overriden, the protected bool "skipTests" should be handled
|
||||
* so that the content of the test is not analyzed.
|
||||
*/
|
||||
override void visit(const Unittest unittest_)
|
||||
{
|
||||
if (!skipTests)
|
||||
unittest_.accept(this);
|
||||
}
|
||||
/**
|
||||
* Visits a unittest.
|
||||
*
|
||||
* When overriden, the protected bool "skipTests" should be handled
|
||||
* so that the content of the test is not analyzed.
|
||||
*/
|
||||
override void visit(const Unittest unittest_)
|
||||
{
|
||||
if (!skipTests)
|
||||
unittest_.accept(this);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
bool inAggregate;
|
||||
bool skipTests;
|
||||
bool skipTests;
|
||||
|
||||
template visitTemplate(T)
|
||||
{
|
||||
|
|
|
@ -27,7 +27,7 @@ import std.algorithm : map;
|
|||
* }
|
||||
* ---
|
||||
*/
|
||||
class BuiltinPropertyNameCheck : BaseAnalyzer
|
||||
final class BuiltinPropertyNameCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import dsymbol.scope_;
|
|||
/**
|
||||
* Check for uses of the comma expression.
|
||||
*/
|
||||
class CommaExpressionCheck : BaseAnalyzer
|
||||
final class CommaExpressionCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import dscanner.analysis.base;
|
|||
import dscanner.analysis.helpers;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
class ConstructorCheck : BaseAnalyzer
|
||||
final class ConstructorCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import dsymbol.scope_;
|
|||
/**
|
||||
* Checks for use of the deprecated 'delete' keyword
|
||||
*/
|
||||
class DeleteCheck : BaseAnalyzer
|
||||
final class DeleteCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import dsymbol.scope_ : Scope;
|
|||
* Checks for duplicate attributes such as @property, @safe,
|
||||
* @trusted, @system, pure, and nothrow
|
||||
*/
|
||||
class DuplicateAttributeCheck : BaseAnalyzer
|
||||
final class DuplicateAttributeCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ void doNothing(string, size_t, size_t, string, bool)
|
|||
{
|
||||
}
|
||||
|
||||
class EnumArrayLiteralCheck : BaseAnalyzer
|
||||
final class EnumArrayLiteralCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import std.stdio;
|
|||
/**
|
||||
* Requires unittests to be explicitly annotated with either @safe or @system
|
||||
*/
|
||||
class ExplicitlyAnnotatedUnittestCheck : BaseAnalyzer
|
||||
final class ExplicitlyAnnotatedUnittestCheck : BaseAnalyzer
|
||||
{
|
||||
enum string KEY = "dscanner.style.explicitly_annotated_unittest";
|
||||
enum string MESSAGE = "A unittest should be annotated with at least @safe or @system";
|
||||
|
|
|
@ -20,387 +20,387 @@ final class FinalAttributeChecker : BaseAnalyzer
|
|||
|
||||
private:
|
||||
|
||||
enum string KEY = "dscanner.useless.final";
|
||||
enum string MSGB = "Useless final attribute, %s";
|
||||
enum string KEY = "dscanner.useless.final";
|
||||
enum string MSGB = "Useless final attribute, %s";
|
||||
|
||||
static struct MESSAGE
|
||||
{
|
||||
static immutable struct_i = "structs cannot be subclassed";
|
||||
static immutable union_i = "unions cannot be subclassed";
|
||||
static immutable class_t = "templated functions declared within a class are never virtual";
|
||||
static immutable class_p = "private functions declared within a class are never virtual";
|
||||
static immutable class_f = "functions declared within a final class are never virtual";
|
||||
static immutable class_s = "static functions are never virtual";
|
||||
static immutable interface_t = "templated functions declared within an interface are never virtual";
|
||||
static immutable struct_f = "functions declared within a struct are never virtual";
|
||||
static immutable union_f = "functions declared within an union are never virtual";
|
||||
static immutable func_n = "nested functions are never virtual";
|
||||
static immutable func_g = "global functions are never virtual";
|
||||
}
|
||||
static struct MESSAGE
|
||||
{
|
||||
static immutable struct_i = "structs cannot be subclassed";
|
||||
static immutable union_i = "unions cannot be subclassed";
|
||||
static immutable class_t = "templated functions declared within a class are never virtual";
|
||||
static immutable class_p = "private functions declared within a class are never virtual";
|
||||
static immutable class_f = "functions declared within a final class are never virtual";
|
||||
static immutable class_s = "static functions are never virtual";
|
||||
static immutable interface_t = "templated functions declared within an interface are never virtual";
|
||||
static immutable struct_f = "functions declared within a struct are never virtual";
|
||||
static immutable union_f = "functions declared within an union are never virtual";
|
||||
static immutable func_n = "nested functions are never virtual";
|
||||
static immutable func_g = "global functions are never virtual";
|
||||
}
|
||||
|
||||
enum Parent
|
||||
{
|
||||
module_,
|
||||
struct_,
|
||||
union_,
|
||||
class_,
|
||||
function_,
|
||||
interface_
|
||||
}
|
||||
enum Parent
|
||||
{
|
||||
module_,
|
||||
struct_,
|
||||
union_,
|
||||
class_,
|
||||
function_,
|
||||
interface_
|
||||
}
|
||||
|
||||
bool[] _private;
|
||||
bool _finalAggregate;
|
||||
bool _alwaysStatic;
|
||||
bool _blockStatic;
|
||||
Parent _parent = Parent.module_;
|
||||
bool[] _private;
|
||||
bool _finalAggregate;
|
||||
bool _alwaysStatic;
|
||||
bool _blockStatic;
|
||||
Parent _parent = Parent.module_;
|
||||
|
||||
void addError(T)(T t, string msg)
|
||||
{
|
||||
import std.format : format;
|
||||
const size_t lne = t.name.line;
|
||||
const size_t col = t.name.column;
|
||||
addErrorMessage(lne, col, KEY, MSGB.format(msg));
|
||||
}
|
||||
void addError(T)(T t, string msg)
|
||||
{
|
||||
import std.format : format;
|
||||
const size_t lne = t.name.line;
|
||||
const size_t col = t.name.column;
|
||||
addErrorMessage(lne, col, KEY, MSGB.format(msg));
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
///
|
||||
this(string fileName, bool skipTests = false)
|
||||
{
|
||||
super(fileName, null, skipTests);
|
||||
_private.length = 1;
|
||||
}
|
||||
///
|
||||
this(string fileName, bool skipTests = false)
|
||||
{
|
||||
super(fileName, null, skipTests);
|
||||
_private.length = 1;
|
||||
}
|
||||
|
||||
override void visit(const(StructDeclaration) sd)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.struct_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
sd.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
override void visit(const(StructDeclaration) sd)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.struct_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
sd.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
|
||||
override void visit(const(InterfaceDeclaration) id)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.interface_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
id.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
override void visit(const(InterfaceDeclaration) id)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.interface_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
id.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
|
||||
override void visit(const(UnionDeclaration) ud)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.union_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
ud.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
override void visit(const(UnionDeclaration) ud)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.union_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
ud.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
|
||||
override void visit(const(ClassDeclaration) cd)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.class_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
cd.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
override void visit(const(ClassDeclaration) cd)
|
||||
{
|
||||
const Parent saved = _parent;
|
||||
_parent = Parent.class_;
|
||||
_private.length += 1;
|
||||
_alwaysStatic = false;
|
||||
cd.accept(this);
|
||||
_private.length -= 1;
|
||||
_parent = saved;
|
||||
}
|
||||
|
||||
override void visit(const(MixinTemplateDeclaration) mtd)
|
||||
{
|
||||
// can't really know where it'll be mixed (class |final class | struct ?)
|
||||
}
|
||||
override void visit(const(MixinTemplateDeclaration) mtd)
|
||||
{
|
||||
// can't really know where it'll be mixed (class |final class | struct ?)
|
||||
}
|
||||
|
||||
override void visit(const(TemplateDeclaration) mtd)
|
||||
{
|
||||
// regular template are also mixable
|
||||
}
|
||||
override void visit(const(TemplateDeclaration) mtd)
|
||||
{
|
||||
// regular template are also mixable
|
||||
}
|
||||
|
||||
override void visit(const(AttributeDeclaration) decl)
|
||||
{
|
||||
if (_parent == Parent.class_ && decl.attribute &&
|
||||
decl.attribute.attribute == tok!"static")
|
||||
_alwaysStatic = true;
|
||||
}
|
||||
override void visit(const(AttributeDeclaration) decl)
|
||||
{
|
||||
if (_parent == Parent.class_ && decl.attribute &&
|
||||
decl.attribute.attribute == tok!"static")
|
||||
_alwaysStatic = true;
|
||||
}
|
||||
|
||||
override void visit(const(Declaration) d)
|
||||
{
|
||||
import std.algorithm.iteration : filter;
|
||||
import std.algorithm.searching : canFind;
|
||||
override void visit(const(Declaration) d)
|
||||
{
|
||||
import std.algorithm.iteration : filter;
|
||||
import std.algorithm.searching : canFind;
|
||||
|
||||
const Parent savedParent = _parent;
|
||||
const Parent savedParent = _parent;
|
||||
|
||||
bool undoBlockStatic;
|
||||
if (_parent == Parent.class_ && d.attributes &&
|
||||
d.attributes.canFind!(a => a.attribute == tok!"static"))
|
||||
{
|
||||
_blockStatic = true;
|
||||
undoBlockStatic = true;
|
||||
}
|
||||
bool undoBlockStatic;
|
||||
if (_parent == Parent.class_ && d.attributes &&
|
||||
d.attributes.canFind!(a => a.attribute == tok!"static"))
|
||||
{
|
||||
_blockStatic = true;
|
||||
undoBlockStatic = true;
|
||||
}
|
||||
|
||||
scope(exit)
|
||||
{
|
||||
d.accept(this);
|
||||
_parent = savedParent;
|
||||
if (undoBlockStatic)
|
||||
_blockStatic = false;
|
||||
}
|
||||
scope(exit)
|
||||
{
|
||||
d.accept(this);
|
||||
_parent = savedParent;
|
||||
if (undoBlockStatic)
|
||||
_blockStatic = false;
|
||||
}
|
||||
|
||||
if (!d.attributeDeclaration &&
|
||||
!d.classDeclaration &&
|
||||
!d.structDeclaration &&
|
||||
!d.unionDeclaration &&
|
||||
!d.interfaceDeclaration &&
|
||||
!d.functionDeclaration)
|
||||
return;
|
||||
if (!d.attributeDeclaration &&
|
||||
!d.classDeclaration &&
|
||||
!d.structDeclaration &&
|
||||
!d.unionDeclaration &&
|
||||
!d.interfaceDeclaration &&
|
||||
!d.functionDeclaration)
|
||||
return;
|
||||
|
||||
if (d.attributeDeclaration && d.attributeDeclaration.attribute)
|
||||
{
|
||||
const tp = d.attributeDeclaration.attribute.attribute.type;
|
||||
_private[$-1] = isProtection(tp) & (tp == tok!"private");
|
||||
}
|
||||
if (d.attributeDeclaration && d.attributeDeclaration.attribute)
|
||||
{
|
||||
const tp = d.attributeDeclaration.attribute.attribute.type;
|
||||
_private[$-1] = isProtection(tp) & (tp == tok!"private");
|
||||
}
|
||||
|
||||
const bool isFinal = d.attributes
|
||||
.canFind!(a => a.attribute.type == tok!"final");
|
||||
const bool isFinal = d.attributes
|
||||
.canFind!(a => a.attribute.type == tok!"final");
|
||||
|
||||
const bool isStaticOnce = d.attributes
|
||||
.canFind!(a => a.attribute.type == tok!"static");
|
||||
const bool isStaticOnce = d.attributes
|
||||
.canFind!(a => a.attribute.type == tok!"static");
|
||||
|
||||
// determine if private
|
||||
const bool changeProtectionOnce = d.attributes
|
||||
.canFind!(a => a.attribute.type.isProtection);
|
||||
// determine if private
|
||||
const bool changeProtectionOnce = d.attributes
|
||||
.canFind!(a => a.attribute.type.isProtection);
|
||||
|
||||
const bool isPrivateOnce = d.attributes
|
||||
.canFind!(a => a.attribute.type == tok!"private");
|
||||
const bool isPrivateOnce = d.attributes
|
||||
.canFind!(a => a.attribute.type == tok!"private");
|
||||
|
||||
bool isPrivate;
|
||||
bool isPrivate;
|
||||
|
||||
if (isPrivateOnce)
|
||||
isPrivate = true;
|
||||
else if (_private[$-1] && !changeProtectionOnce)
|
||||
isPrivate = true;
|
||||
if (isPrivateOnce)
|
||||
isPrivate = true;
|
||||
else if (_private[$-1] && !changeProtectionOnce)
|
||||
isPrivate = true;
|
||||
|
||||
// check final aggregate type
|
||||
if (d.classDeclaration || d.structDeclaration || d.unionDeclaration)
|
||||
{
|
||||
_finalAggregate = isFinal;
|
||||
if (_finalAggregate && savedParent == Parent.module_)
|
||||
{
|
||||
if (d.structDeclaration)
|
||||
addError(d.structDeclaration, MESSAGE.struct_i);
|
||||
else if (d.unionDeclaration)
|
||||
addError(d.unionDeclaration, MESSAGE.union_i);
|
||||
}
|
||||
}
|
||||
// check final aggregate type
|
||||
if (d.classDeclaration || d.structDeclaration || d.unionDeclaration)
|
||||
{
|
||||
_finalAggregate = isFinal;
|
||||
if (_finalAggregate && savedParent == Parent.module_)
|
||||
{
|
||||
if (d.structDeclaration)
|
||||
addError(d.structDeclaration, MESSAGE.struct_i);
|
||||
else if (d.unionDeclaration)
|
||||
addError(d.unionDeclaration, MESSAGE.union_i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!d.functionDeclaration)
|
||||
return;
|
||||
if (!d.functionDeclaration)
|
||||
return;
|
||||
|
||||
// check final functions
|
||||
_parent = Parent.function_;
|
||||
const(FunctionDeclaration) fd = d.functionDeclaration;
|
||||
// check final functions
|
||||
_parent = Parent.function_;
|
||||
const(FunctionDeclaration) fd = d.functionDeclaration;
|
||||
|
||||
if (isFinal) final switch(savedParent)
|
||||
{
|
||||
case Parent.class_:
|
||||
if (fd.templateParameters)
|
||||
addError(fd, MESSAGE.class_t);
|
||||
if (isPrivate)
|
||||
addError(fd, MESSAGE.class_p);
|
||||
else if (isStaticOnce || _alwaysStatic || _blockStatic)
|
||||
addError(fd, MESSAGE.class_s);
|
||||
else if (_finalAggregate)
|
||||
addError(fd, MESSAGE.class_f);
|
||||
break;
|
||||
case Parent.interface_:
|
||||
if (fd.templateParameters)
|
||||
addError(fd, MESSAGE.interface_t);
|
||||
break;
|
||||
case Parent.struct_:
|
||||
addError(fd, MESSAGE.struct_f);
|
||||
break;
|
||||
case Parent.union_:
|
||||
addError(fd, MESSAGE.union_f);
|
||||
break;
|
||||
case Parent.function_:
|
||||
addError(fd, MESSAGE.func_n);
|
||||
break;
|
||||
case Parent.module_:
|
||||
addError(fd, MESSAGE.func_g);
|
||||
break;
|
||||
}
|
||||
if (isFinal) final switch(savedParent)
|
||||
{
|
||||
case Parent.class_:
|
||||
if (fd.templateParameters)
|
||||
addError(fd, MESSAGE.class_t);
|
||||
if (isPrivate)
|
||||
addError(fd, MESSAGE.class_p);
|
||||
else if (isStaticOnce || _alwaysStatic || _blockStatic)
|
||||
addError(fd, MESSAGE.class_s);
|
||||
else if (_finalAggregate)
|
||||
addError(fd, MESSAGE.class_f);
|
||||
break;
|
||||
case Parent.interface_:
|
||||
if (fd.templateParameters)
|
||||
addError(fd, MESSAGE.interface_t);
|
||||
break;
|
||||
case Parent.struct_:
|
||||
addError(fd, MESSAGE.struct_f);
|
||||
break;
|
||||
case Parent.union_:
|
||||
addError(fd, MESSAGE.union_f);
|
||||
break;
|
||||
case Parent.function_:
|
||||
addError(fd, MESSAGE.func_n);
|
||||
break;
|
||||
case Parent.module_:
|
||||
addError(fd, MESSAGE.func_g);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@system unittest
|
||||
{
|
||||
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
||||
import std.stdio : stderr;
|
||||
import std.format : format;
|
||||
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
||||
import std.stdio : stderr;
|
||||
import std.format : format;
|
||||
|
||||
StaticAnalysisConfig sac = disabledConfig();
|
||||
sac.final_attribute_check = Check.enabled;
|
||||
StaticAnalysisConfig sac = disabledConfig();
|
||||
sac.final_attribute_check = Check.enabled;
|
||||
|
||||
// pass
|
||||
// pass
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){void foo(){}}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){void foo(){}}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
struct S{}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
struct S{}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
union U{}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
union U{}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{public final void foo(){}}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{public final void foo(){}}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
final class Foo{static struct Bar{}}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
final class Foo{static struct Bar{}}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: public final void foo(){}}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: public final void foo(){}}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: public: final void foo(){}}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: public: final void foo(){}}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: public: final void foo(){}}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: public: final void foo(){}}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Impl
|
||||
{
|
||||
private:
|
||||
static if (true)
|
||||
{
|
||||
protected final void _wrap_getSource() {}
|
||||
}
|
||||
}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Impl
|
||||
{
|
||||
private:
|
||||
static if (true)
|
||||
{
|
||||
protected final void _wrap_getSource() {}
|
||||
}
|
||||
}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
mixin template Impl()
|
||||
{
|
||||
protected final void mixin_template_can() {}
|
||||
}
|
||||
}, sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
mixin template Impl()
|
||||
{
|
||||
protected final void mixin_template_can() {}
|
||||
}
|
||||
}, sac);
|
||||
|
||||
// fail
|
||||
// fail
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
final void foo(){} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_g)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
final void foo(){} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_g)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_n)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_n)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo()
|
||||
{
|
||||
static if (true)
|
||||
final class A{ private: final protected void foo(){}} // [warn]: %s
|
||||
}
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo()
|
||||
{
|
||||
static if (true)
|
||||
final class A{ private: final protected void foo(){}} // [warn]: %s
|
||||
}
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
final struct Foo{} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.struct_i)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
final struct Foo{} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.struct_i)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
final union Foo{} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.union_i)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
final union Foo{} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.union_i)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo{private: final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
interface Foo{final void foo(T)(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.interface_t)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
interface Foo{final void foo(T)(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.interface_t)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
final class Foo{final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
final class Foo{final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
private: final class Foo {public: private final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
private: final class Foo {public: private final void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo {final static void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo {final static void foo(){}} // [warn]: %s
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo
|
||||
{
|
||||
void foo(){}
|
||||
static: final void foo(){} // [warn]: %s
|
||||
}
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo
|
||||
{
|
||||
void foo(){}
|
||||
static: final void foo(){} // [warn]: %s
|
||||
}
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
|
||||
), sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo
|
||||
{
|
||||
void foo(){}
|
||||
static{ final void foo(){}} // [warn]: %s
|
||||
void foo(){}
|
||||
}
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
|
||||
), sac);
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo
|
||||
{
|
||||
void foo(){}
|
||||
static{ final void foo(){}} // [warn]: %s
|
||||
void foo(){}
|
||||
}
|
||||
}c.format(
|
||||
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
|
||||
), sac);
|
||||
|
||||
stderr.writeln("Unittest for FinalAttributeChecker passed.");
|
||||
stderr.writeln("Unittest for FinalAttributeChecker passed.");
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import dsymbol.scope_ : Scope;
|
|||
/**
|
||||
* Checks for use of the deprecated floating point comparison operators.
|
||||
*/
|
||||
class FloatOperatorCheck : BaseAnalyzer
|
||||
final class FloatOperatorCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import dsymbol.scope_;
|
|||
* const int getStuff() {}
|
||||
* ---
|
||||
*/
|
||||
class FunctionAttributeCheck : BaseAnalyzer
|
||||
final class FunctionAttributeCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import std.stdio;
|
|||
* Checks for public declarations without a documented unittests.
|
||||
* For now, variable and enum declarations aren't checked.
|
||||
*/
|
||||
class HasPublicExampleCheck : BaseAnalyzer
|
||||
final class HasPublicExampleCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import std.range;
|
|||
/**
|
||||
Checks whether all if constraints have the same indention as their declaration.
|
||||
*/
|
||||
class IfConstraintsIndentCheck : BaseAnalyzer
|
||||
final class IfConstraintsIndentCheck : BaseAnalyzer
|
||||
{
|
||||
///
|
||||
this(string fileName, const(Token)[] tokens, bool skipTests = false)
|
||||
|
@ -174,13 +174,13 @@ if (R == int) // [warn]: %s
|
|||
assertAnalyzerWarnings(q{
|
||||
Num abs(Num)(Num x) @safe pure nothrow
|
||||
if (is(typeof(Num.init >= 0)) && is(typeof(-Num.init)) &&
|
||||
!(is(Num* : const(ifloat*)) || is(Num* : const(idouble*))
|
||||
|| is(Num* : const(ireal*))))
|
||||
!(is(Num* : const(ifloat*)) || is(Num* : const(idouble*))
|
||||
|| is(Num* : const(ireal*))))
|
||||
{
|
||||
static if (isFloatingPoint!(Num))
|
||||
return fabs(x);
|
||||
else
|
||||
return x >= 0 ? x : -x;
|
||||
static if (isFloatingPoint!(Num))
|
||||
return fabs(x);
|
||||
else
|
||||
return x >= 0 ? x : -x;
|
||||
}
|
||||
}, sac);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import dparse.formatter;
|
|||
import dscanner.analysis.base;
|
||||
import dsymbol.scope_ : Scope;
|
||||
|
||||
class IfStatementCheck : BaseAnalyzer
|
||||
final class IfStatementCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
||||
|
|
|
@ -20,7 +20,7 @@ import dsymbol.scope_ : Scope;
|
|||
* $(LI == expressions where the left and right are the same)
|
||||
* )
|
||||
*/
|
||||
class IfElseSameCheck : BaseAnalyzer
|
||||
final class IfElseSameCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import std.stdio;
|
|||
/**
|
||||
* Checks the sortedness of module imports
|
||||
*/
|
||||
class ImportSortednessCheck : BaseAnalyzer
|
||||
final class ImportSortednessCheck : BaseAnalyzer
|
||||
{
|
||||
enum string KEY = "dscanner.style.imports_sortedness";
|
||||
enum string MESSAGE = "The imports are not sorted in alphabetical order";
|
||||
|
@ -347,8 +347,8 @@ unittest
|
|||
import foo;
|
||||
import foo.bar;
|
||||
import fooa;
|
||||
import std.range : Take;
|
||||
import std.range.primitives : isInputRange, walkLength;
|
||||
import std.range : Take;
|
||||
import std.range.primitives : isInputRange, walkLength;
|
||||
}, sac);
|
||||
|
||||
// condition declaration
|
||||
|
|
|
@ -13,7 +13,7 @@ import dparse.lexer;
|
|||
/**
|
||||
* Checks for incorrect infinite range definitions
|
||||
*/
|
||||
class IncorrectInfiniteRangeCheck : BaseAnalyzer
|
||||
final class IncorrectInfiniteRangeCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import dscanner.analysis.helpers;
|
|||
/**
|
||||
* Checks for labels and variables that have the same name.
|
||||
*/
|
||||
class LabelVarNameCheck : BaseAnalyzer
|
||||
final class LabelVarNameCheck : BaseAnalyzer
|
||||
{
|
||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
||||
{
|
||||
|
@ -228,7 +228,7 @@ unittest
|
|||
|
||||
unittest
|
||||
{
|
||||
version(LittleEndian) { enum string NAME = "UTF-16LE"; }
|
||||
version(LittleEndian) { enum string NAME = "UTF-16LE"; }
|
||||
else version(BigEndian) { enum string NAME = "UTF-16BE"; }
|
||||
}
|
||||
|
||||
|
@ -267,8 +267,8 @@ unittest
|
|||
|
||||
unittest
|
||||
{
|
||||
int aa;
|
||||
struct a { int a; }
|
||||
int aa;
|
||||
struct a { int a; }
|
||||
}
|
||||
|
||||
}c, sac);
|
||||
|
|
|
@ -9,39 +9,39 @@ import dparse.ast;
|
|||
import dparse.lexer;
|
||||
import dscanner.analysis.base;
|
||||
|
||||
class LambdaReturnCheck : BaseAnalyzer
|
||||
final class LambdaReturnCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
this(string fileName, bool skipTests = false)
|
||||
{
|
||||
super(fileName, null, skipTests);
|
||||
}
|
||||
|
||||
override void visit(const FunctionLiteralExpression fLit)
|
||||
{
|
||||
if (fLit.assignExpression is null)
|
||||
return;
|
||||
const UnaryExpression unary = cast(const UnaryExpression) fLit.assignExpression;
|
||||
if (unary is null)
|
||||
return;
|
||||
if (unary.primaryExpression is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.parameters !is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.identifier != tok!"")
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.functionBody is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.functionBody.blockStatement is null)
|
||||
return;
|
||||
addErrorMessage(fLit.line, fLit.column, KEY, "This lambda returns a lambda. Add parenthesis to clarify.");
|
||||
}
|
||||
override void visit(const FunctionLiteralExpression fLit)
|
||||
{
|
||||
if (fLit.assignExpression is null)
|
||||
return;
|
||||
const UnaryExpression unary = cast(const UnaryExpression) fLit.assignExpression;
|
||||
if (unary is null)
|
||||
return;
|
||||
if (unary.primaryExpression is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.parameters !is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.identifier != tok!"")
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.functionBody is null)
|
||||
return;
|
||||
if (unary.primaryExpression.functionLiteralExpression.functionBody.blockStatement is null)
|
||||
return;
|
||||
addErrorMessage(fLit.line, fLit.column, KEY, "This lambda returns a lambda. Add parenthesis to clarify.");
|
||||
}
|
||||
|
||||
private:
|
||||
enum KEY = "dscanner.confusing.lambda_returns_lambda";
|
||||
enum KEY = "dscanner.confusing.lambda_returns_lambda";
|
||||
}
|
||||
|
||||
version(Windows) {/*because of newline in code*/} else
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_;
|
|||
/**
|
||||
* Checks for subtraction from a .length property. This is usually a bug.
|
||||
*/
|
||||
class LengthSubtractionCheck : BaseAnalyzer
|
||||
final class LengthSubtractionCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import std.typecons : tuple, Tuple;
|
|||
/**
|
||||
* Checks for lines longer than 120 characters
|
||||
*/
|
||||
class LineLengthCheck : BaseAnalyzer
|
||||
final class LineLengthCheck : BaseAnalyzer
|
||||
{
|
||||
///
|
||||
this(string fileName, const(Token)[] tokens, bool skipTests = false)
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_;
|
|||
* Checks for local imports that import all symbols.
|
||||
* See_also: $(LINK https://issues.dlang.org/show_bug.cgi?id=10378)
|
||||
*/
|
||||
class LocalImportCheck : BaseAnalyzer
|
||||
final class LocalImportCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import dsymbol.scope_;
|
|||
* if (a && (b || c)) // good
|
||||
* ---
|
||||
*/
|
||||
class LogicPrecedenceCheck : BaseAnalyzer
|
||||
final class LogicPrecedenceCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_ : Scope;
|
|||
/**
|
||||
* Checks for long and hard-to-read number literals
|
||||
*/
|
||||
class NumberStyleCheck : BaseAnalyzer
|
||||
final class NumberStyleCheck : BaseAnalyzer
|
||||
{
|
||||
public:
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
|
|
@ -17,7 +17,7 @@ import dsymbol.scope_ : Scope;
|
|||
* Checks that opEquals, opCmp, toHash, 'opCast', and toString are either const,
|
||||
* immutable, or inout.
|
||||
*/
|
||||
class ObjectConstCheck : BaseAnalyzer
|
||||
final class ObjectConstCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
@ -122,12 +122,12 @@ unittest
|
|||
|
||||
class Bat
|
||||
{
|
||||
const: override string toString() { return "foo"; } // ok
|
||||
const: override string toString() { return "foo"; } // ok
|
||||
}
|
||||
|
||||
class Fox
|
||||
{
|
||||
const{ override string toString() { return "foo"; }} // ok
|
||||
const{ override string toString() { return "foo"; }} // ok
|
||||
}
|
||||
|
||||
// Will warn, because none are const
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_ : Scope;
|
|||
* Checks for when a class/struct has the method opEquals without toHash, or
|
||||
* toHash without opEquals.
|
||||
*/
|
||||
class OpEqualsWithoutToHashCheck : BaseAnalyzer
|
||||
final class OpEqualsWithoutToHashCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import dsymbol.scope_ : Scope;
|
|||
* }
|
||||
* ---
|
||||
*/
|
||||
class PokemonExceptionCheck : BaseAnalyzer
|
||||
final class PokemonExceptionCheck : BaseAnalyzer
|
||||
{
|
||||
enum MESSAGE = "Catching Error or Throwable is almost always a bad idea.";
|
||||
enum string KEY = "dscanner.suspicious.catch_em_all";
|
||||
|
|
|
@ -6,7 +6,9 @@ module dscanner.analysis.properly_documented_public_functions;
|
|||
|
||||
import dparse.lexer;
|
||||
import dparse.ast;
|
||||
import dparse.formatter : astFmt = format;
|
||||
import dscanner.analysis.base : BaseAnalyzer;
|
||||
import dscanner.utils : safeAccess;
|
||||
|
||||
import std.format : format;
|
||||
import std.range.primitives;
|
||||
|
@ -20,7 +22,7 @@ import std.stdio;
|
|||
- Ddoc params entries without a parameter trigger warnings as well
|
||||
- RETURNS: (except if it's void, only functions)
|
||||
*/
|
||||
class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
||||
final class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
||||
{
|
||||
enum string MISSING_PARAMS_KEY = "dscanner.style.doc_missing_params";
|
||||
enum string MISSING_PARAMS_MESSAGE = "Parameter %s isn't documented in the `Params` section.";
|
||||
|
@ -33,6 +35,9 @@ class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
|||
enum string MISSING_RETURNS_KEY = "dscanner.style.doc_missing_returns";
|
||||
enum string MISSING_RETURNS_MESSAGE = "A public function needs to contain a `Returns` section.";
|
||||
|
||||
enum string MISSING_THROW_KEY = "dscanner.style.doc_missing_throw";
|
||||
enum string MISSING_THROW_MESSAGE = "An instance of `%s` is thrown but not documented in the `Throws` section";
|
||||
|
||||
///
|
||||
this(string fileName, bool skipTests = false)
|
||||
{
|
||||
|
@ -46,6 +51,46 @@ class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
|||
postCheckSeenDdocParams();
|
||||
}
|
||||
|
||||
override void visit(const UnaryExpression decl)
|
||||
{
|
||||
const IdentifierOrTemplateInstance iot = safeAccess(decl)
|
||||
.functionCallExpression.unaryExpression.primaryExpression
|
||||
.identifierOrTemplateInstance;
|
||||
|
||||
Type newNamedType(N)(N name)
|
||||
{
|
||||
Type t = new Type;
|
||||
t.type2 = new Type2;
|
||||
t.type2.typeIdentifierPart = new TypeIdentifierPart;
|
||||
t.type2.typeIdentifierPart.identifierOrTemplateInstance = new IdentifierOrTemplateInstance;
|
||||
t.type2.typeIdentifierPart.identifierOrTemplateInstance.identifier = name;
|
||||
return t;
|
||||
}
|
||||
|
||||
// enforce(condition);
|
||||
if (iot && iot.identifier.text == "enforce")
|
||||
{
|
||||
thrown ~= newNamedType(Token(tok!"identifier", "Exception", 0, 0, 0));
|
||||
}
|
||||
else if (iot && iot.templateInstance && iot.templateInstance.identifier.text == "enforce")
|
||||
{
|
||||
// enforce!Type(condition);
|
||||
if (const TemplateSingleArgument tsa = safeAccess(iot.templateInstance)
|
||||
.templateArguments.templateSingleArgument)
|
||||
{
|
||||
thrown ~= newNamedType(tsa.token);
|
||||
}
|
||||
// enforce!(Type)(condition);
|
||||
else if (const TemplateArgumentList tal = safeAccess(iot.templateInstance)
|
||||
.templateArguments.templateArgumentList)
|
||||
{
|
||||
if (tal.items.length && tal.items[0].type)
|
||||
thrown ~= tal.items[0].type;
|
||||
}
|
||||
}
|
||||
decl.accept(this);
|
||||
}
|
||||
|
||||
override void visit(const Declaration decl)
|
||||
{
|
||||
import std.algorithm.searching : any;
|
||||
|
@ -57,6 +102,24 @@ class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
|||
tokPackage = tok!"package",
|
||||
tokPublic = tok!"public";
|
||||
|
||||
// Nested funcs for `Throws`
|
||||
bool decNestedFunc;
|
||||
if (decl.functionDeclaration)
|
||||
{
|
||||
nestedFuncs++;
|
||||
decNestedFunc = true;
|
||||
}
|
||||
scope(exit)
|
||||
{
|
||||
if (decNestedFunc)
|
||||
nestedFuncs--;
|
||||
}
|
||||
if (nestedFuncs > 1)
|
||||
{
|
||||
decl.accept(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decl.attributes.length > 0)
|
||||
{
|
||||
const bool isPublic = !decl.attributes.map!`a.attribute`.any!(x => x == tokPrivate ||
|
||||
|
@ -114,20 +177,69 @@ class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
|||
override void visit(const FunctionDeclaration decl)
|
||||
{
|
||||
import std.algorithm.searching : all, any;
|
||||
import std.array : Appender;
|
||||
|
||||
// ignore header declaration for now
|
||||
if (decl.functionBody is null)
|
||||
return;
|
||||
|
||||
auto comment = setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
|
||||
if (nestedFuncs == 1)
|
||||
thrown.length = 0;
|
||||
// detect ThrowStatement only if not nothrow
|
||||
if (!decl.attributes.any!(a => a.attribute.text == "nothrow"))
|
||||
{
|
||||
decl.accept(this);
|
||||
if (nestedFuncs == 1 && !hasThrowSection(decl.comment))
|
||||
foreach(t; thrown)
|
||||
{
|
||||
Appender!(char[]) app;
|
||||
astFmt(&app, t);
|
||||
addErrorMessage(decl.name.line, decl.name.column, MISSING_THROW_KEY,
|
||||
MISSING_THROW_MESSAGE.format(app.data));
|
||||
}
|
||||
}
|
||||
|
||||
checkDdocParams(decl.name.line, decl.name.column, decl.parameters, decl.templateParameters);
|
||||
if (nestedFuncs == 1)
|
||||
{
|
||||
auto comment = setLastDdocParams(decl.name.line, decl.name.column, decl.comment);
|
||||
checkDdocParams(decl.name.line, decl.name.column, decl.parameters, decl.templateParameters);
|
||||
enum voidType = tok!"void";
|
||||
if (decl.returnType is null || decl.returnType.type2.builtinType != voidType)
|
||||
if (!(comment.isDitto || withinTemplate || comment.sections.any!(s => s.name == "Returns")))
|
||||
addErrorMessage(decl.name.line, decl.name.column,
|
||||
MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
enum voidType = tok!"void";
|
||||
// remove thrown Type that are caught
|
||||
override void visit(const TryStatement ts)
|
||||
{
|
||||
import std.algorithm.iteration : filter;
|
||||
import std.algorithm.searching : canFind;
|
||||
import std.array : array;
|
||||
|
||||
if (decl.returnType is null || decl.returnType.type2.builtinType != voidType)
|
||||
if (!(comment.isDitto || withinTemplate || comment.sections.any!(s => s.name == "Returns")))
|
||||
addErrorMessage(decl.name.line, decl.name.column, MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE);
|
||||
ts.accept(this);
|
||||
|
||||
if (ts.catches)
|
||||
thrown = thrown.filter!(a => !ts.catches.catches
|
||||
.canFind!(b => b.type == a))
|
||||
.array;
|
||||
}
|
||||
|
||||
override void visit(const ThrowStatement ts)
|
||||
{
|
||||
import std.algorithm.searching : canFind;
|
||||
|
||||
ts.accept(this);
|
||||
if (ts.expression && ts.expression.items.length == 1)
|
||||
if (const UnaryExpression ue = cast(UnaryExpression) ts.expression.items[0])
|
||||
{
|
||||
if (ue.newExpression && ue.newExpression.type &&
|
||||
!thrown.canFind!(a => a == ue.newExpression.type))
|
||||
{
|
||||
thrown ~= ue.newExpression.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
@ -135,6 +247,7 @@ class ProperlyDocumentedPublicFunctions : BaseAnalyzer
|
|||
private:
|
||||
bool islastSeenVisibilityLabelPublic;
|
||||
bool withinTemplate;
|
||||
size_t nestedFuncs;
|
||||
|
||||
static struct Function
|
||||
{
|
||||
|
@ -145,6 +258,8 @@ private:
|
|||
}
|
||||
Function lastSeenFun;
|
||||
|
||||
const(Type)[] thrown;
|
||||
|
||||
// find invalid ddoc parameters (i.e. they don't occur in a function declaration)
|
||||
void postCheckSeenDdocParams()
|
||||
{
|
||||
|
@ -159,6 +274,15 @@ private:
|
|||
lastSeenFun.active = false;
|
||||
}
|
||||
|
||||
bool hasThrowSection(string commentText)
|
||||
{
|
||||
import std.algorithm.searching : canFind;
|
||||
import ddoc.comments : parseComment;
|
||||
|
||||
const comment = parseComment(commentText, null);
|
||||
return comment.isDitto || comment.sections.canFind!(s => s.name == "Throws");
|
||||
}
|
||||
|
||||
auto setLastDdocParams(size_t line, size_t column, string commentText)
|
||||
{
|
||||
import ddoc.comments : parseComment;
|
||||
|
@ -167,11 +291,14 @@ private:
|
|||
import std.array : array;
|
||||
|
||||
const comment = parseComment(commentText, null);
|
||||
if (withinTemplate) {
|
||||
if (withinTemplate)
|
||||
{
|
||||
const paramSection = comment.sections.find!(s => s.name == "Params");
|
||||
if (!paramSection.empty)
|
||||
lastSeenFun.ddocParams ~= paramSection[0].mapping.map!(a => a[0]).array;
|
||||
} else if (!comment.isDitto) {
|
||||
}
|
||||
else if (!comment.isDitto)
|
||||
{
|
||||
// check old function for invalid ddoc params
|
||||
if (lastSeenFun.active)
|
||||
postCheckSeenDdocParams();
|
||||
|
@ -214,11 +341,17 @@ private:
|
|||
foreach (p; params.parameters)
|
||||
{
|
||||
string templateName;
|
||||
if (const t = p.type)
|
||||
if (const t2 = t.type2)
|
||||
if (const tip = t2.typeIdentifierPart)
|
||||
if (const iot = tip.identifierOrTemplateInstance)
|
||||
|
||||
if (auto iot = safeAccess(p).type.type2
|
||||
.typeIdentifierPart.identifierOrTemplateInstance.unwrap)
|
||||
{
|
||||
templateName = iot.identifier.text;
|
||||
}
|
||||
else if (auto iot = safeAccess(p).type.type2.type.type2
|
||||
.typeIdentifierPart.identifierOrTemplateInstance.unwrap)
|
||||
{
|
||||
templateName = iot.identifier.text;
|
||||
}
|
||||
|
||||
const idx = tlList.countUntil(templateName);
|
||||
if (idx >= 0)
|
||||
|
@ -243,8 +376,8 @@ private:
|
|||
|
||||
void checkDdocParams(size_t line, size_t column, const TemplateParameters templateParams)
|
||||
{
|
||||
|
||||
if (lastSeenFun.active && templateParams !is null && templateParams.templateParameterList !is null)
|
||||
if (lastSeenFun.active && templateParams !is null &&
|
||||
templateParams.templateParameterList !is null)
|
||||
checkDdocParams(line, column, templateParams.templateParameterList.items);
|
||||
}
|
||||
|
||||
|
@ -718,28 +851,28 @@ unittest
|
|||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
Counts elements in the given
|
||||
$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
|
||||
until the given predicate is true for one of the given $(D needles).
|
||||
Counts elements in the given
|
||||
$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
|
||||
until the given predicate is true for one of the given $(D needles).
|
||||
|
||||
Params:
|
||||
Params:
|
||||
val = A stupid parameter
|
||||
|
||||
Returns: Awesome values.
|
||||
Returns: Awesome values.
|
||||
+/
|
||||
string bar(string val){}
|
||||
}c, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
Counts elements in the given
|
||||
$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
|
||||
until the given predicate is true for one of the given $(D needles).
|
||||
Counts elements in the given
|
||||
$(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
|
||||
until the given predicate is true for one of the given $(D needles).
|
||||
|
||||
Params:
|
||||
Params:
|
||||
val = A stupid parameter
|
||||
|
||||
Returns: Awesome values.
|
||||
Returns: Awesome values.
|
||||
+/
|
||||
template bar(string val){}
|
||||
}c, sac);
|
||||
|
@ -824,6 +957,233 @@ unittest
|
|||
}, sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
An awesome description.
|
||||
|
||||
Params:
|
||||
items = things to put.
|
||||
|
||||
Returns: Awesome values.
|
||||
+/
|
||||
void put(Range)(const(Range) items) if (canPutConstRange!Range)
|
||||
{}
|
||||
}, sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
Throw but likely catched.
|
||||
+/
|
||||
void bar(){
|
||||
try{throw new Exception("bla");throw new Error("bla");}
|
||||
catch(Exception){} catch(Error){}}
|
||||
}c, sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
Simple case
|
||||
+/
|
||||
void bar(){throw new Exception("bla");} // [warn]: %s
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Exception")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
Supposed to be documented
|
||||
|
||||
Throws: Exception if...
|
||||
+/
|
||||
void bar(){throw new Exception("bla");}
|
||||
}c.format(
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
rethrow
|
||||
+/
|
||||
void bar(){try throw new Exception("bla"); catch(Exception) throw new Error();} // [warn]: %s
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Error")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
trust nothrow before everything
|
||||
+/
|
||||
void bar() nothrow {try throw new Exception("bla"); catch(Exception) assert(0);}
|
||||
}c, sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
case of throw in nested func
|
||||
+/
|
||||
void bar() // [warn]: %s
|
||||
{
|
||||
void foo(){throw new AssertError("bla");}
|
||||
foo();
|
||||
}
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
case of throw in nested func but caught
|
||||
+/
|
||||
void bar()
|
||||
{
|
||||
void foo(){throw new AssertError("bla");}
|
||||
try foo();
|
||||
catch (AssertError){}
|
||||
}
|
||||
}c, sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
case of double throw in nested func but only 1 caught
|
||||
+/
|
||||
void bar() // [warn]: %s
|
||||
{
|
||||
void foo(){throw new AssertError("bla");throw new Error("bla");}
|
||||
try foo();
|
||||
catch (Error){}
|
||||
}
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
enforce
|
||||
+/
|
||||
void bar() // [warn]: %s
|
||||
{
|
||||
enforce(condition);
|
||||
}
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("Exception")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
enforce
|
||||
+/
|
||||
void bar() // [warn]: %s
|
||||
{
|
||||
enforce!AssertError(condition);
|
||||
}
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
enforce
|
||||
+/
|
||||
void bar() // [warn]: %s
|
||||
{
|
||||
enforce!(AssertError)(condition);
|
||||
}
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
||||
), sac);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
StaticAnalysisConfig sac = disabledConfig;
|
||||
sac.properly_documented_public_functions = Check.enabled;
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
/++
|
||||
enforce
|
||||
+/
|
||||
void foo() // [warn]: %s
|
||||
{
|
||||
void bar()
|
||||
{
|
||||
enforce!AssertError(condition);
|
||||
}
|
||||
bar();
|
||||
}
|
||||
|
||||
}c.format(
|
||||
ProperlyDocumentedPublicFunctions.MISSING_THROW_MESSAGE.format("AssertError")
|
||||
), sac);
|
||||
}
|
||||
|
||||
// https://github.com/dlang-community/D-Scanner/issues/583
|
||||
unittest
|
||||
{
|
||||
|
@ -835,10 +1195,10 @@ unittest
|
|||
Implements the homonym function (also known as `accumulate`)
|
||||
|
||||
Returns:
|
||||
the accumulated `result`
|
||||
the accumulated `result`
|
||||
|
||||
Params:
|
||||
fun = one or more functions
|
||||
fun = one or more functions
|
||||
+/
|
||||
template reduce(fun...)
|
||||
if (fun.length >= 1)
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_ : Scope;
|
|||
* Checks for .. expressions where the left side is larger than the right. This
|
||||
* is almost always a mistake.
|
||||
*/
|
||||
class BackwardsRangeCheck : BaseAnalyzer
|
||||
final class BackwardsRangeCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import std.range : empty, front, walkLength;
|
|||
/**
|
||||
* Checks for redundant attributes. At the moment only visibility attributes.
|
||||
*/
|
||||
class RedundantAttributesCheck : BaseAnalyzer
|
||||
final class RedundantAttributesCheck : BaseAnalyzer
|
||||
{
|
||||
this(string fileName, const(Scope)* sc, bool skipTests = false)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ import dsymbol.scope_ : Scope;
|
|||
/**
|
||||
* Checks for redundant parenthesis
|
||||
*/
|
||||
class RedundantParenCheck : BaseAnalyzer
|
||||
final class RedundantParenCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import dsymbol.scope_ : Scope;
|
|||
/**
|
||||
* Checks for redundant storage classes such immutable and __gshared, static and __gshared
|
||||
*/
|
||||
class RedundantStorageClassCheck : BaseAnalyzer
|
||||
final class RedundantStorageClassCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
enum string REDUNDANT_VARIABLE_ATTRIBUTES = "Variable declaration for `%s` has redundant attributes (%-(`%s`%|, %)).";
|
||||
|
|
|
@ -22,7 +22,7 @@ import dscanner.utils : safeAccess;
|
|||
*
|
||||
* However, it's more likely that this is a mistake.
|
||||
*/
|
||||
class StaticIfElse : BaseAnalyzer
|
||||
final class StaticIfElse : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import dparse.ast;
|
|||
import dparse.lexer;
|
||||
import dscanner.analysis.base;
|
||||
|
||||
class StatsCollector : BaseAnalyzer
|
||||
final class StatsCollector : BaseAnalyzer
|
||||
{
|
||||
alias visit = ASTVisitor.visit;
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ import dsymbol.scope_;
|
|||
/**
|
||||
* Checks that `@trusted` is only applied to a a single function
|
||||
*/
|
||||
class TrustTooMuchCheck : BaseAnalyzer
|
||||
final class TrustTooMuchCheck : BaseAnalyzer
|
||||
{
|
||||
private:
|
||||
|
||||
static immutable MESSAGE = "Trusting a whole scope is a bad idea, " ~
|
||||
"`@trusted` should only be attached to a single function";
|
||||
"`@trusted` should only be attached to the functions individually";
|
||||
static immutable string KEY = "dscanner.trust_too_much";
|
||||
|
||||
bool checkAtAttribute = true;
|
||||
|
|
|
@ -17,7 +17,7 @@ import std.stdio;
|
|||
* Checks for undocumented public declarations. Ignores some operator overloads,
|
||||
* main functions, and functions whose name starts with "get" or "set".
|
||||
*/
|
||||
class UndocumentedDeclarationCheck : BaseAnalyzer
|
||||
final class UndocumentedDeclarationCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import dparse.lexer;
|
|||
/**
|
||||
* Checks for variables that could have been declared const or immutable
|
||||
*/
|
||||
class UnmodifiedFinder : BaseAnalyzer
|
||||
final class UnmodifiedFinder : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
@ -138,6 +138,7 @@ class UnmodifiedFinder : BaseAnalyzer
|
|||
mixin PartsMightModify!AsmPrimaryExp;
|
||||
mixin PartsMightModify!IndexExpression;
|
||||
mixin PartsMightModify!FunctionCallExpression;
|
||||
mixin PartsMightModify!NewExpression;
|
||||
mixin PartsMightModify!IdentifierOrTemplateChain;
|
||||
mixin PartsMightModify!ReturnStatement;
|
||||
|
||||
|
@ -332,40 +333,51 @@ bool isValueTypeSimple(const Type type) pure nothrow @nogc
|
|||
|
||||
@system unittest
|
||||
{
|
||||
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
||||
import std.stdio : stderr;
|
||||
import std.format : format;
|
||||
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
||||
import std.stdio : stderr;
|
||||
import std.format : format;
|
||||
|
||||
StaticAnalysisConfig sac = disabledConfig();
|
||||
sac.could_be_immutable_check = Check.enabled;
|
||||
StaticAnalysisConfig sac = disabledConfig();
|
||||
sac.could_be_immutable_check = Check.enabled;
|
||||
|
||||
// fails
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){int i = 1;} // [warn]: Variable i is never modified and could have been declared const or immutable.
|
||||
}, sac);
|
||||
void foo(){int i = 1;} // [warn]: Variable i is never modified and could have been declared const or immutable.
|
||||
}, sac);
|
||||
|
||||
// pass
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){const(int) i;}
|
||||
}, sac);
|
||||
// pass
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){immutable(int)* i;}
|
||||
}, sac);
|
||||
void foo(){const(int) i;}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){enum i = 1;}
|
||||
}, sac);
|
||||
void foo(){immutable(int)* i;}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){E e = new E;}
|
||||
}, sac);
|
||||
void foo(){enum i = 1;}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){auto e = new E;}
|
||||
}, sac);
|
||||
void foo(){E e = new E;}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void foo(){auto e = new E;}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
void issue640()
|
||||
{
|
||||
size_t i1;
|
||||
new Foo(i1);
|
||||
|
||||
size_t i2;
|
||||
foo(i2);
|
||||
}
|
||||
}, sac);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import std.algorithm : all;
|
|||
/**
|
||||
* Checks for unused variables.
|
||||
*/
|
||||
class UnusedVariableCheck : BaseAnalyzer
|
||||
final class UnusedVariableCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
@ -311,7 +311,7 @@ class UnusedVariableCheck : BaseAnalyzer
|
|||
interestDepth++;
|
||||
withStatetement.expression.accept(this);
|
||||
interestDepth--;
|
||||
withStatetement.statementNoCaseNoDefault.accept(this);
|
||||
withStatetement.declarationOrStatement.accept(this);
|
||||
}
|
||||
|
||||
override void visit(const Parameter parameter)
|
||||
|
|
|
@ -23,7 +23,7 @@ auto filterChars(string chars, S)(S str)
|
|||
/**
|
||||
* Checks for asserts that always succeed
|
||||
*/
|
||||
class UselessAssertCheck : BaseAnalyzer
|
||||
final class UselessAssertCheck : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
|
|
@ -7,10 +7,7 @@ module dscanner.analysis.vcall_in_ctor;
|
|||
import dscanner.analysis.base;
|
||||
import dscanner.utils;
|
||||
import dparse.ast, dparse.lexer;
|
||||
import std.algorithm: among;
|
||||
import std.algorithm.iteration : filter;
|
||||
import std.algorithm.searching : find;
|
||||
import std.range.primitives : empty;
|
||||
import std.algorithm.searching : canFind;
|
||||
import std.range: retro;
|
||||
|
||||
/**
|
||||
|
@ -19,7 +16,7 @@ import std.range: retro;
|
|||
* When not used carefully, virtual calls from constructors can lead to a call
|
||||
* in a derived instance that's not yet constructed.
|
||||
*/
|
||||
class VcallCtorChecker : BaseAnalyzer
|
||||
final class VcallCtorChecker : BaseAnalyzer
|
||||
{
|
||||
alias visit = BaseAnalyzer.visit;
|
||||
|
||||
|
@ -183,16 +180,21 @@ public:
|
|||
bool pop;
|
||||
scope(exit) if (pop)
|
||||
popVirtual;
|
||||
|
||||
const bool hasAttribs = d.attributes !is null;
|
||||
const bool hasStatic = hasAttribs ? d.attributes.canFind!(a => a.attribute.type == tok!"static") : false;
|
||||
const bool hasFinal = hasAttribs ? d.attributes.canFind!(a => a.attribute.type == tok!"final") : false;
|
||||
|
||||
if (d.attributes) foreach (attr; d.attributes.retro)
|
||||
{
|
||||
if (attr.attribute == tok!"public" || attr.attribute == tok!"protected" ||
|
||||
attr.attribute == tok!"package")
|
||||
if (!hasStatic &&
|
||||
(attr.attribute == tok!"public" || attr.attribute == tok!"protected"))
|
||||
{
|
||||
pushVirtual(true);
|
||||
pop = true;
|
||||
break;
|
||||
}
|
||||
else if (attr.attribute == tok!"private")
|
||||
else if (hasStatic || attr.attribute == tok!"private" || attr.attribute == tok!"package")
|
||||
{
|
||||
pushVirtual(false);
|
||||
pop = true;
|
||||
|
@ -201,13 +203,12 @@ public:
|
|||
}
|
||||
|
||||
// final class... final function
|
||||
const bool pf = !d.attributes.find!(a => a.attribute.type == tok!"final").empty;
|
||||
if ((d.classDeclaration || d.functionDeclaration) && pf)
|
||||
if ((d.classDeclaration || d.functionDeclaration) && hasFinal)
|
||||
pushIsFinal(true);
|
||||
|
||||
d.accept(this);
|
||||
|
||||
if ((d.classDeclaration || d.functionDeclaration) && pf)
|
||||
if ((d.classDeclaration || d.functionDeclaration) && hasFinal)
|
||||
popIsFinal;
|
||||
}
|
||||
|
||||
|
@ -242,11 +243,14 @@ public:
|
|||
bool virtualOnce;
|
||||
bool notVirtualOnce;
|
||||
|
||||
const bool hasAttribs = d.attributes !is null;
|
||||
const bool hasStatic = hasAttribs ? d.attributes.canFind!(a => a.attribute.type == tok!"static") : false;
|
||||
|
||||
// handle "private", "public"... for this declaration
|
||||
if (d.attributes) foreach (attr; d.attributes.retro)
|
||||
{
|
||||
if (attr.attribute == tok!"public" || attr.attribute == tok!"protected" ||
|
||||
attr.attribute == tok!"package")
|
||||
if (!hasStatic &&
|
||||
(attr.attribute == tok!"public" || attr.attribute == tok!"protected"))
|
||||
{
|
||||
if (!isVirtual)
|
||||
{
|
||||
|
@ -254,7 +258,7 @@ public:
|
|||
break;
|
||||
}
|
||||
}
|
||||
else if (attr.attribute == tok!"private")
|
||||
else if (hasStatic || attr.attribute == tok!"private" || attr.attribute == tok!"package")
|
||||
{
|
||||
if (isVirtual)
|
||||
{
|
||||
|
@ -382,6 +386,22 @@ unittest
|
|||
}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo
|
||||
{
|
||||
static void nonVirtual();
|
||||
this(){nonVirtual();}
|
||||
}
|
||||
}, sac);
|
||||
|
||||
assertAnalyzerWarnings(q{
|
||||
class Foo
|
||||
{
|
||||
package void nonVirtual();
|
||||
this(){nonVirtual();}
|
||||
}
|
||||
}, sac);
|
||||
|
||||
import std.stdio: writeln;
|
||||
writeln("Unittest for VcallCtorChecker passed");
|
||||
}
|
||||
|
|
|
@ -8,18 +8,20 @@ module dscanner.dscanner_version;
|
|||
/**
|
||||
* Human-readable version number
|
||||
*/
|
||||
enum DSCANNER_VERSION = "v0.4.0";
|
||||
enum DEFAUULT_DSCANNER_VERSION = "v0.5.5";
|
||||
|
||||
version (Windows)
|
||||
version (built_with_dub)
|
||||
{
|
||||
enum DSCANNER_VERSION = import("dubhash.txt");
|
||||
}
|
||||
else version (built_with_dub)
|
||||
else version (Windows)
|
||||
{
|
||||
enum DSCANNER_VERSION = DEFAUULT_DSCANNER_VERSION;
|
||||
}
|
||||
else
|
||||
{
|
||||
/**
|
||||
* Current build's Git commit hash
|
||||
*/
|
||||
enum GIT_HASH = import("githash.txt");
|
||||
enum DSCANNER_VERSION = import("githash.txt");
|
||||
}
|
||||
|
|
|
@ -139,12 +139,7 @@ else
|
|||
|
||||
if (printVersion)
|
||||
{
|
||||
version (Windows)
|
||||
writeln(DSCANNER_VERSION);
|
||||
else version (built_with_dub)
|
||||
writeln(DSCANNER_VERSION);
|
||||
else
|
||||
write(DSCANNER_VERSION, " ", GIT_HASH);
|
||||
write(DSCANNER_VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ if (is(M == class))
|
|||
__traits(getMember, m, member) = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// General usage
|
||||
@safe unittest
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7487970b58f4a2c0d495679329a8a2857111f3fd
|
||||
Subproject commit b7778fd6bf5f9aaaa87dd27f989cefbf9b3b365f
|
Loading…
Reference in New Issue