Merge branch 'master' into phobos

This commit is contained in:
Sebastian Wilzbach 2018-05-30 12:57:42 +02:00
commit 1b88b41f6b
59 changed files with 998 additions and 565 deletions

View File

@ -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)

View File

@ -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

@ -1 +1 @@
Subproject commit 239b137b280c06864b73fcc1d00b75e06568d4c2
Subproject commit e018446b028b92c34398032e698c05c0919630ee

View File

@ -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\\\"));\""
]
}

@ -1 +1 @@
Subproject commit 9d7333ec17f116a05712a24df139ff2f212a9867
Subproject commit cecaff8037a60db2a51c9bded4802c87d938a44e

@ -1 +1 @@
Subproject commit 970efe34e66fc7b3cb93a6ec59984099908070c5
Subproject commit cf102ff8e848fb18d2ce7056ae61dafb5333012d

View File

@ -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}

25
release-windows.sh Executable file
View File

@ -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

View File

@ -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;

View File

@ -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";

View File

@ -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;

View File

@ -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";

View File

@ -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.");
}

View File

@ -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)

View File

@ -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)
{

View File

@ -27,7 +27,7 @@ import std.algorithm : map;
* }
* ---
*/
class BuiltinPropertyNameCheck : BaseAnalyzer
final class BuiltinPropertyNameCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -15,7 +15,7 @@ void doNothing(string, size_t, size_t, string, bool)
{
}
class EnumArrayLiteralCheck : BaseAnalyzer
final class EnumArrayLiteralCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;

View File

@ -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";

View File

@ -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.");
}

View File

@ -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;

View File

@ -21,7 +21,7 @@ import dsymbol.scope_;
* const int getStuff() {}
* ---
*/
class FunctionAttributeCheck : BaseAnalyzer
final class FunctionAttributeCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -13,7 +13,7 @@ import dparse.lexer;
/**
* Checks for incorrect infinite range definitions
*/
class IncorrectInfiniteRangeCheck : BaseAnalyzer
final class IncorrectInfiniteRangeCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -19,7 +19,7 @@ import dsymbol.scope_;
* if (a && (b || c)) // good
* ---
*/
class LogicPrecedenceCheck : BaseAnalyzer
final class LogicPrecedenceCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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";

View File

@ -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)

View File

@ -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;

View File

@ -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)
{

View File

@ -13,7 +13,7 @@ import dsymbol.scope_ : Scope;
/**
* Checks for redundant parenthesis
*/
class RedundantParenCheck : BaseAnalyzer
final class RedundantParenCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;

View File

@ -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`%|, %)).";

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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)

View File

@ -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;

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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;
}

View File

@ -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