From cd2b2e0af4b713f83e95e35578244352b6a16fe9 Mon Sep 17 00:00:00 2001 From: lucica28 <57060141+lucica28@users.noreply.github.com> Date: Fri, 19 May 2023 10:25:25 +0300 Subject: [PATCH] update dmd and include the API needed for semantic analysis (#66) * update dmd and include the API needed for semantic analysis * update libparse + initial implementation for properly documented public functions * test * refactor * update workflows * delete unused code --- .github/workflows/default.yml | 7 +- build.bat | 28 +- dmd | 2 +- dub.json | 7 +- makefile | 28 +- src/dscanner/analysis/base.d | 8 +- src/dscanner/analysis/helpers.d | 120 +++ .../properly_documented_public_functions.d | 966 ++++++------------ src/dscanner/analysis/run.d | 119 ++- src/dscanner/imports.d | 8 +- src/dscanner/utils.d | 5 +- 11 files changed, 581 insertions(+), 717 deletions(-) diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index b21e42c..2332ea3 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -107,6 +107,10 @@ jobs: sudo apt-get install gdc-12 -y gdc-12 --version + # - name: Setup upterm session + # if: ${{ matrix.build.type == 'make' && matrix.host == 'macos-latest'}} + # uses: lhotari/action-upterm@v1 + # Compile D-Scanner and execute all tests without dub - name: Build and test without dub if: ${{ matrix.build.type == 'make' }} @@ -119,7 +123,8 @@ jobs: ./build.bat ./build.bat test else - make "-j$(nproc)" all test + NUM_PROC=$(nproc || getconf _NPROCESSORS_ONLN || 1) + make "-j$((NUM_PROC / 2))" all test fi # Compile D-Scanner and execute all tests using a specific dependency version diff --git a/build.bat b/build.bat index 8bffe67..8da9fd0 100644 --- a/build.bat +++ b/build.bat @@ -18,8 +18,8 @@ if %githashsize% == 0 ( move /y bin\githash_.txt bin\githash.txt ) -set DFLAGS=-O -release -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS -Jbin -Jdmd %MFLAGS% -set TESTFLAGS=-g -w -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS -Jbin -Jdmd +set DFLAGS=-O -release -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS -Jbin -Jdmd -Jdmd\compiler\src\dmd\res %MFLAGS% +set TESTFLAGS=-g -w -version=StdLoggerDisableWarning -version=CallbackAPI -version=DMDLIB -version=MARS -Jbin -Jdmd -Jdmd\compiler\src\dmd\res set CORE= set LIBDPARSE= set STD= @@ -29,6 +29,8 @@ set DSYMBOL= set CONTAINERS= set LIBDDOC= +SET DMD_FRONTEND_SRC=objc_glue.obj clone.obj transitivevisitor.obj iasm.obj iasmdmd.obj canthrow.obj tokens.obj optimize.obj func.obj semantic2.obj dvarstats.obj ph2.obj code.obj cdef.obj xmm.obj out.obj elfobj.obj glocal.obj dvec.obj code_x86.obj iasm2.obj string2.obj file2.obj obj.obj go.obj inliner.obj cc.obj bcomplex.obj mscoffobj.obj ptrntab.obj dlist.obj pdata.obj fp.obj cod3.obj os.obj cgelem.obj dcode.obj disasm86.obj exh.obj blockopt.obj aarray.obj cg.obj newman.obj dwarfdbginf.obj codebuilder.obj var.obj cod2.obj machobj.obj cgobj.obj cod4.obj dtype.obj cv4.obj backend.obj el.obj cgcod.obj cv8.obj dwarf.obj evalu8.obj ty.obj mem.obj cgxmm.obj gdag.obj gother.obj goh.obj cgcv.obj debugprint.obj cgsched.obj dwarfeh.obj cgreg.obj backconfig.obj gloop.obj divcoeff.obj cod5.obj dwarf2.obj cg87.obj nteh.obj dcgcv.obj util2.obj compress.obj type.obj elpicpie.obj gsroa.obj cgcs.obj ee.obj symbol.obj barray.obj melf.obj oper.obj cgcse.obj rtlsym.obj mscoff.obj drtlsym.obj symtab.obj dt.obj mach.obj cod1.obj global.obj filespec.obj gflow.obj elem.obj cgen.obj md5.obj chkformat.obj argtypes_sysv_x64.obj sideeffect.obj denum.obj apply.obj e2ir.obj typinf.obj statement.obj arraytypes.obj blockexit.obj init.obj scanomf.obj utils.obj parsetimevisitor.obj errorsink.obj scanmscoff.obj initsem.obj arrayop.obj nogc.obj dsymbol.obj hdrgen.obj dmangle.obj astenums.obj libmscoff.obj compiler.obj foreachvar.obj scanmach.obj dcast.obj tocsym.obj tocvdebug.obj semantic3.obj builtin.obj sapply.obj printast.obj dtemplate.obj importc.obj file_manager.obj dclass.obj argtypes_x86.obj glue.obj statement_rewrite_walker.obj target.obj aggregate.obj stringtable.obj ctfloat.obj response.obj strtold.obj port.obj aav.obj env.obj optional.obj filename.obj man.obj rootobject.obj complex.obj hash.obj region.obj utf.obj speller.obj rmem.obj array.obj longdouble.obj bitarray.obj eh.obj strictvisitor.obj permissivevisitor.obj lambdacomp.obj ctfeexpr.obj cparse.obj imphint.obj delegatize.obj access.obj identifier.obj todt.obj dmsc.obj entity.obj impcnvtab.obj dimport.obj lexer.obj dinifile.obj libomf.obj vsoptions.obj dstruct.obj aliasthis.obj ctorflow.obj errors.obj astcodegen.obj mtype.obj dtoh.obj argtypes_aarch64.obj cpreprocess.obj dmdparams.obj lib.obj id.obj parse.obj doc.obj scanelf.obj iasmgcc.obj cppmanglewin.obj stmtstate.obj ob.obj expression.obj declaration.obj location.obj dinterpret.obj inline.obj bitfields.obj string.obj int128.obj file.obj outbuffer.obj nspace.obj gluelayer.obj json.obj toir.obj intrange.obj cond.obj constfold.obj dversion.obj staticassert.obj dmodule.obj traits.obj opover.obj link.obj toctype.obj staticcond.obj statementsem.obj globals.obj libmach.obj toobj.obj s2ir.obj inlinecost.obj objc.obj visitor.obj asttypename.obj mustuse.obj dsymbolsem.obj frontend.obj safe.obj dscope.obj attrib.obj ast_node.obj escape.obj cli.obj templateparamsem.obj libelf.obj console.obj cppmangle.obj astbase.obj dmacro.obj typesem.obj expressionsem.obj + set DMD_ROOT_SRC= for %%x in (dmd\compiler\src\dmd\common\*.d) do set DMD_ROOT_SRC=!DMD_ROOT_SRC! %%x for %%x in (dmd\compiler\src\dmd\root\*.d) do set DMD_ROOT_SRC=!DMD_ROOT_SRC! %%x @@ -67,9 +69,21 @@ for %%x in (DCD\dsymbol\src\dsymbol\conversion\*.d) do set DSYMBOL=!DSYMBOL! %%x for %%x in (containers\src\containers\*.d) do set CONTAINERS=!CONTAINERS! %%x for %%x in (containers\src\containers\internal\*.d) do set CONTAINERS=!CONTAINERS! %%x +for %%x in (dmd\compiler\src\dmd\common\*.d) do %DC% %DFLAGS% -c %%x -od. -I"dmd\compiler\src" +for %%x in (dmd\compiler\src\dmd\root\*.d) do %DC% %DFLAGS% -c %%x -od. -I"dmd\compiler\src" +for %%x in (dmd\compiler\src\dmd\backend\*.d) do %DC% %DFLAGS% -c %%x -od. -I"dmd\compiler\src" +for %%x in (dmd\compiler\src\dmd\*.d) do %DC% %DFLAGS% -c %%x -od. -I"dmd\compiler\src" + +%DC% %DFLAGS% -c dmd\compiler\src\dmd\backend\iasm.d -od. -ofiasm2.obj -I"dmd\compiler\src" +%DC% %DFLAGS% -c dmd\compiler\src\dmd\common\string.d -od. -ofstring2.obj -I"dmd\compiler\src" +%DC% %DFLAGS% -c dmd\compiler\src\dmd\common\file.d -od. -offile2.obj -I"dmd\compiler\src" + if "%1" == "test" goto test_cmd @echo on +dir +echo %DMD_FRONTEND_SRC% + %DC% %MFLAGS%^ %CORE%^ %STD%^ @@ -79,9 +93,7 @@ if "%1" == "test" goto test_cmd %INIFILED%^ %DSYMBOL%^ %CONTAINERS%^ - %DMD_ROOT_SRC%^ - %DMD_LEXER_SRC%^ - %DMD_PARSER_SRC%^ + %DMD_FRONTEND_SRC%^ %DFLAGS%^ -I"libdparse\src"^ -I"DCD\dsymbol\src"^ @@ -102,14 +114,13 @@ set TESTNAME="bin\dscanner-unittest" %INIFILED%^ %DSYMBOL%^ %CONTAINERS%^ - %DMD_ROOT_SRC%^ - %DMD_LEXER_SRC%^ - %DMD_PARSER_SRC%^ + %DMD_FRONTEND_SRC%^ -I"libdparse\src"^ -I"DCD\dsymbol\src"^ -I"containers\src"^ -I"libddoc\src"^ -I"dmd\compiler\src"^ + -I"dmd\compiler\src\dmd\res"^ -lib %TESTFLAGS%^ -of%TESTNAME%.lib if exist %TESTNAME%.lib %DC% %MFLAGS%^ @@ -124,6 +135,7 @@ if exist %TESTNAME%.lib %DC% %MFLAGS%^ -I"libddoc\src"^ -I"libddoc\common\source"^ -I"dmd\compiler\src"^ + -I"dmd\compiler\src\dmd\res"^ -unittest^ %TESTFLAGS%^ -of%TESTNAME%.exe diff --git a/dmd b/dmd index 020685c..a422035 160000 --- a/dmd +++ b/dmd @@ -1 +1 @@ -Subproject commit 020685c85b4fde7d50511716dc98dfc5dc97ef2b +Subproject commit a4220358ecfcffe7ea38ab4a1996ffc5a5331f22 diff --git a/dub.json b/dub.json index c1b8d6a..f228f04 100644 --- a/dub.json +++ b/dub.json @@ -16,9 +16,10 @@ "inifiled": "~>1.3.1", "emsi_containers": "~>0.9.0", "libddoc": "~>0.8.0", - "dmd:root": "~master", - "dmd:lexer": "~master", - "dmd:parser": "~master" + "dmd": { + "repository": "git+https://github.com/dlang/dmd.git", + "version": "a4220358ecfcffe7ea38ab4a1996ffc5a5331f22" + } }, "targetPath" : "bin", "stringImportPaths" : [ diff --git a/makefile b/makefile index 9440bb2..0047405 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,7 @@ .PHONY: all test clean +.DEFAULT_GOAL := all + DC ?= dmd GIT ?= git DMD := $(DC) @@ -7,11 +9,19 @@ GDC := gdc LDC := ldc2 DMD_ROOT_SRC := \ $(shell find dmd/compiler/src/dmd/common -name "*.d")\ - $(shell find dmd/compiler/src/dmd/root -name "*.d") + $(shell find dmd/compiler/src/dmd/root -name "*.d")\ + +DMD_FRONTEND_SRC := \ + $(shell find dmd/compiler/src/dmd/common -name "*.d")\ + $(shell find dmd/compiler/src/dmd/root -name "*.d")\ + $(shell find dmd/compiler/src/dmd/backend -name "*.d")\ + $(shell find dmd/compiler/src/dmd -maxdepth 1 -name "*.d" ! -name "mars.d" ) + DMD_LEXER_SRC := \ dmd/compiler/src/dmd/console.d \ dmd/compiler/src/dmd/entity.d \ dmd/compiler/src/dmd/errors.d \ + dmd/compiler/src/dmd/errorsink.d \ dmd/compiler/src/dmd/file_manager.d \ dmd/compiler/src/dmd/globals.d \ dmd/compiler/src/dmd/id.d \ @@ -19,6 +29,7 @@ DMD_LEXER_SRC := \ dmd/compiler/src/dmd/lexer.d \ dmd/compiler/src/dmd/tokens.d \ dmd/compiler/src/dmd/utils.d \ + dmd/compiler/src/dmd/location.d \ $(DMD_ROOT_SRC) DMD_PARSER_SRC := \ @@ -39,7 +50,8 @@ LIB_SRC := \ $(shell find libdparse/src/dparse/ -name "*.d")\ $(shell find libddoc/src -name "*.d") \ $(shell find libddoc/common/source -name "*.d") \ - $(DMD_PARSER_SRC) + $(DMD_FRONTEND_SRC) + PROJECT_SRC := $(shell find src/ -name "*.d") SRC := $(LIB_SRC) $(PROJECT_SRC) @@ -78,17 +90,17 @@ LDC_DEBUG_VERSIONS = -d-version=dparse_verbose GDC_VERSIONS = -fversion=StdLoggerDisableWarning -fversion=CallbackAPI -fversion=DMDLIB -fversion=MARS GDC_DEBUG_VERSIONS = -fversion=dparse_verbose -DC_FLAGS += -Jbin -Jdmd +DC_FLAGS += -Jbin -Jdmd -Jdmd/compiler/src/dmd/res override DMD_FLAGS += $(DFLAGS) -w -release -O -od${OBJ_DIR} override LDC_FLAGS += $(DFLAGS) -O5 -release -oq override GDC_FLAGS += $(DFLAGS) -O3 -frelease -fall-instantiations override GDC_TEST_FLAGS += -fall-instantiations -DC_TEST_FLAGS += -g -Jbin -Jdmd +DC_TEST_FLAGS += -g -Jbin -Jdmd -Jdmd/compiler/src/dmd/res override DMD_TEST_FLAGS += -w -DC_DEBUG_FLAGS := -g -Jbin -Jdmd +DC_DEBUG_FLAGS := -g -Jbin -Jdmd -Jdmd/compiler/src/dmd/res ifeq ($(DC), $(filter $(DC), dmd ldmd2 gdmd)) VERSIONS := $(DMD_VERSIONS) @@ -113,7 +125,13 @@ SHELL:=/usr/bin/env bash GITHASH = bin/githash.txt +FIRST_RUN_FLAG := $(OBJ_DIR)/$(DC)/first_run.flag + $(OBJ_DIR)/$(DC)/%.o: %.d + if [ ! -f $(FIRST_RUN_FLAG) ]; then \ + ${DC} -run dmd/config.d bin VERSION /etc; \ + touch $(FIRST_RUN_FLAG); \ + fi ${DC} ${DC_FLAGS} ${VERSIONS} ${INCLUDE_PATHS} -c $< ${WRITE_TO_TARGET_NAME} $(UT_OBJ_DIR)/$(DC)/%.o: %.d diff --git a/src/dscanner/analysis/base.d b/src/dscanner/analysis/base.d index aa36817..5e40c9c 100644 --- a/src/dscanner/analysis/base.d +++ b/src/dscanner/analysis/base.d @@ -10,6 +10,8 @@ import std.meta : AliasSeq; import std.string; import std.sumtype; import dmd.transitivevisitor; +import dmd.visitor; +import dmd.func; import core.stdc.string; import std.conv : to; @@ -909,9 +911,9 @@ unittest * Visitor that implements the AST traversal logic. * Supports collecting error messages */ -extern(C++) class BaseAnalyzerDmd(AST) : ParseTimeTransitiveVisitor!AST +extern(C++) class BaseAnalyzerDmd : SemanticTimeTransitiveVisitor { - alias visit = ParseTimeTransitiveVisitor!AST.visit; + alias visit = SemanticTimeTransitiveVisitor.visit; extern(D) this(string fileName, bool skipTests = false) { @@ -934,7 +936,7 @@ extern(C++) class BaseAnalyzerDmd(AST) : ParseTimeTransitiveVisitor!AST return _messages[].array; } - override void visit(AST.UnitTestDeclaration ud) + override void visit(UnitTestDeclaration ud) { if (!skipTests) super.visit(ud); diff --git a/src/dscanner/analysis/helpers.d b/src/dscanner/analysis/helpers.d index bd9e4a3..7111fae 100644 --- a/src/dscanner/analysis/helpers.d +++ b/src/dscanner/analysis/helpers.d @@ -22,6 +22,8 @@ import std.experimental.allocator.mallocator; import dmd.parse : Parser; import dmd.astbase : ASTBase; +import dmd.astcodegen; +import dmd.frontend; S between(S)(S value, S before, S after) if (isSomeString!S) { @@ -471,3 +473,121 @@ void assertAnalyzerWarningsDMD(string code, const StaticAnalysisConfig config, b throw new AssertError(message, file, line); } } + +void assertAnalyzerWarningsDMD(string code, const StaticAnalysisConfig config, bool semantic = false, + string file = __FILE__, size_t line = __LINE__) +{ + import dmd.globals : global; + import dscanner.utils : getModuleName; + import std.file : remove, exists; + import std.stdio : File; + import std.path : dirName; + import dmd.arraytypes : Strings; + + import std.stdio : File; + import std.file : exists, remove; + + auto deleteme = "test.txt"; + File f = File(deleteme, "w"); + scope(exit) + { + assert(exists(deleteme)); + remove(deleteme); + } + + f.write(code); + f.close(); + + auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__)))); + + global.params.useUnitTests = true; + global.path = new Strings(); + global.path.push((dmdParentDir ~ "/dmd").ptr); + global.path.push((dmdParentDir ~ "/dmd/druntime/src").ptr); + + initDMD(); + + auto input = cast(char[]) code; + input ~= '\0'; + auto t = dmd.frontend.parseModule(cast(const(char)[]) file, cast(const (char)[]) input); + if (semantic) + t.module_.fullSemantic(); + + MessageSet rawWarnings = analyzeDmd("test.txt", t.module_, getModuleName(t.module_.md), config); + + string[] codeLines = code.splitLines(); + + // Get the warnings ordered by line + string[size_t] warnings; + foreach (rawWarning; rawWarnings[]) + { + // Skip the warning if it is on line zero + immutable size_t rawLine = rawWarning.line; + if (rawLine == 0) + { + stderr.writefln("!!! Skipping warning because it is on line zero:\n%s", + rawWarning.message); + continue; + } + + size_t warnLine = line - 1 + rawLine; + warnings[warnLine] = format("[warn]: %s", rawWarning.message); + } + + // Get all the messages from the comments in the code + string[size_t] messages; + foreach (i, codeLine; codeLines) + { + // Skip if no [warn] comment + if (codeLine.indexOf("// [warn]:") == -1) + continue; + + // Skip if there is no comment or code + immutable string codePart = codeLine.before("// "); + immutable string commentPart = codeLine.after("// "); + if (!codePart.length || !commentPart.length) + continue; + + // Get the line of this code line + size_t lineNo = i + line; + + // Get the message + messages[lineNo] = commentPart; + } + + // Throw an assert error if any messages are not listed in the warnings + foreach (lineNo, message; messages) + { + // No warning + if (lineNo !in warnings) + { + immutable string errors = "Expected warning:\n%s\nFrom source code at (%s:?):\n%s".format(messages[lineNo], + lineNo, codeLines[lineNo - line]); + throw new AssertError(errors, file, lineNo); + } + // Different warning + else if (warnings[lineNo] != messages[lineNo]) + { + immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format( + messages[lineNo], warnings[lineNo], lineNo, codeLines[lineNo - line]); + throw new AssertError(errors, file, lineNo); + } + } + + // Throw an assert error if there were any warnings that were not expected + string[] unexpectedWarnings; + foreach (lineNo, warning; warnings) + { + // Unexpected warning + if (lineNo !in messages) + { + unexpectedWarnings ~= "%s\nFrom source code at (%s:?):\n%s".format(warning, + lineNo, codeLines[lineNo - line]); + } + } + if (unexpectedWarnings.length) + { + immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n"); + throw new AssertError(message, file, line); + } +} diff --git a/src/dscanner/analysis/properly_documented_public_functions.d b/src/dscanner/analysis/properly_documented_public_functions.d index 5bad77d..ba7a989 100644 --- a/src/dscanner/analysis/properly_documented_public_functions.d +++ b/src/dscanner/analysis/properly_documented_public_functions.d @@ -4,15 +4,12 @@ module dscanner.analysis.properly_documented_public_functions; -import dparse.lexer; -import dparse.ast; -import dparse.formatter : astFmt = format; import dscanner.analysis.base; -import dscanner.utils : safeAccess; - import std.format : format; import std.range.primitives; -import std.stdio; +import std.conv : to; +import std.algorithm.searching : canFind, any, find; +import dmd.astcodegen; /** * Requires each public function to contain the following ddoc sections @@ -22,7 +19,7 @@ import std.stdio; - Ddoc params entries without a parameter trigger warnings as well - RETURNS: (except if it's void, only functions) */ -final class ProperlyDocumentedPublicFunctions : BaseAnalyzer +extern(C++) class ProperlyDocumentedPublicFunctions(AST) : BaseAnalyzerDmd { enum string MISSING_PARAMS_KEY = "dscanner.style.doc_missing_params"; enum string MISSING_PARAMS_MESSAGE = "Parameter %s isn't documented in the `Params` section."; @@ -39,268 +36,183 @@ final class ProperlyDocumentedPublicFunctions : BaseAnalyzer enum string MISSING_THROW_MESSAGE = "An instance of `%s` is thrown but not documented in the `Throws` section"; mixin AnalyzerInfo!"properly_documented_public_functions"; + alias visit = BaseAnalyzerDmd.visit; - /// - this(BaseAnalyzerArguments args) + extern(D) this(string fileName, bool skipTests = false) { - super(args); + super(fileName, skipTests); } - override void visit(const Module mod) + override void visit(AST.Module m) { - islastSeenVisibilityLabelPublic = true; - mod.accept(this); + super.visit(m); postCheckSeenDdocParams(); } - override void visit(const UnaryExpression decl) - { - import std.algorithm.searching : canFind; - - 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; - } - - if (inThrowExpression && decl.newExpression && decl.newExpression.type && - !thrown.canFind!(a => a == decl.newExpression.type)) - { - thrown ~= decl.newExpression.type; - } - - // 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 NamedTemplateArgumentList tal = safeAccess(iot.templateInstance) - .templateArguments.namedTemplateArgumentList) - { - 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; - import std.algorithm.iteration : map; - - // skip private symbols - enum tokPrivate = tok!"private", - tokProtected = tok!"protected", - 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 || - x == tokProtected || - x == tokPackage); - // recognize label blocks - if (!hasDeclaration(decl)) - islastSeenVisibilityLabelPublic = isPublic; - - if (!isPublic) - return; - } - - if (islastSeenVisibilityLabelPublic || decl.attributes.map!`a.attribute`.any!(x => x == tokPublic)) - { - // Don't complain about non-documented function declarations - if ((decl.functionDeclaration !is null && decl.functionDeclaration.comment.ptr !is null) || - (decl.templateDeclaration !is null && decl.templateDeclaration.comment.ptr !is null) || - decl.mixinTemplateDeclaration !is null || - (decl.classDeclaration !is null && decl.classDeclaration.comment.ptr !is null) || - (decl.structDeclaration !is null && decl.structDeclaration.comment.ptr !is null)) - decl.accept(this); - } - } - - override void visit(const TemplateDeclaration decl) - { - setLastDdocParams(decl.name, decl.comment); - checkDdocParams(decl.templateParameters); - - withinTemplate = true; - scope(exit) withinTemplate = false; - decl.accept(this); - } - - override void visit(const MixinTemplateDeclaration decl) - { - decl.accept(this); - } - - override void visit(const StructDeclaration decl) - { - setLastDdocParams(decl.name, decl.comment); - checkDdocParams(decl.templateParameters); - decl.accept(this); - } - - override void visit(const ClassDeclaration decl) - { - setLastDdocParams(decl.name, decl.comment); - checkDdocParams(decl.templateParameters); - decl.accept(this); - } - - override void visit(const FunctionDeclaration decl) - { - import std.algorithm.searching : all, any; - import std.array : Appender; - - // ignore header declaration for now - if (!decl.functionBody || (!decl.functionBody.specifiedFunctionBody - && !decl.functionBody.shortenedFunctionBody)) - return; - - if (nestedFuncs == 1) - thrown.length = 0; - // detect ThrowExpression 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, MISSING_THROW_KEY, - MISSING_THROW_MESSAGE.format(app.data)); - } - } - - if (nestedFuncs == 1) - { - auto comment = setLastDdocParams(decl.name, decl.comment); - checkDdocParams(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"))) - { - import dscanner.analysis.auto_function : AutoFunctionChecker; - - const(Token)[] typeRange; - if (decl.returnType !is null) - typeRange = decl.returnType.tokens; - else - typeRange = AutoFunctionChecker.findAutoReturnType(decl); - - if (!typeRange.length) - typeRange = [decl.name]; - addErrorMessage(typeRange, MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE); - } - } - } - - // remove thrown Type that are caught - override void visit(const TryStatement ts) + override void visit(AST.Catch c) { import std.algorithm.iteration : filter; - import std.algorithm.searching : canFind; import std.array : array; - ts.accept(this); - - if (ts.catches) - thrown = thrown.filter!(a => !ts.catches.catches - .canFind!(b => b.type == a)) - .array; + thrown = thrown.filter!(a => a != to!string(c.type.toChars())).array; + super.visit(c); } - override void visit(const ThrowExpression ts) + + override void visit(AST.ThrowStatement t) { - const wasInThrowExpression = inThrowExpression; - inThrowExpression = true; + AST.NewExp ne = t.exp.isNewExp(); + if (ne) + thrown ~= to!string(ne.newtype.toChars()); + + super.visit(t); + } + + override void visit(AST.FuncDeclaration d) + { + nestedFunc++; scope (exit) - inThrowExpression = wasInThrowExpression; - ts.accept(this); - inThrowExpression = false; - } + nestedFunc--; - alias visit = BaseAnalyzer.visit; - -private: - bool islastSeenVisibilityLabelPublic; - bool withinTemplate; - size_t nestedFuncs; - - static struct Function - { - bool active; - Token name; - const(string)[] ddocParams; - bool[string] params; - } - Function lastSeenFun; - - bool inThrowExpression; - const(Type)[] thrown; - - // find invalid ddoc parameters (i.e. they don't occur in a function declaration) - void postCheckSeenDdocParams() - { - import std.format : format; - - if (lastSeenFun.active) - foreach (p; lastSeenFun.ddocParams) - if (p !in lastSeenFun.params) - addErrorMessage(lastSeenFun.name, NON_EXISTENT_PARAMS_KEY, - NON_EXISTENT_PARAMS_MESSAGE.format(p)); - - lastSeenFun.active = false; - } - - bool hasThrowSection(string commentText) - { - import std.algorithm.searching : canFind; + import std.stdio : writeln, writefln; + import std.conv : to; + import std.algorithm.searching : canFind, any, find; + import dmd.dsymbol : Visibility; + import dmd.mtype : Type; import ddoc.comments : parseComment; + import std.algorithm.iteration : map; + import std.array : array; - const comment = parseComment(commentText, null); - return comment.isDitto || comment.sections.canFind!(s => s.name == "Throws"); + if (d.comment is null || d.fbody is null || d.visibility.kind != Visibility.Kind.public_) + { + super.visit(d); + return; + } + + if (nestedFunc == 1) + { + thrown.length = 0; + string[] params; + + if (d.parameters) foreach (p; *d.parameters) + params ~= to!string(p.ident.toString()); + + auto comment = setLastDdocParams(d.loc.linnum, d.loc.charnum, to!string(d.comment)); + checkDdocParams(d.loc.linnum, d.loc.charnum, params, null); + + auto tf = d.type.isTypeFunction(); + if (tf && tf.next != Type.tvoid && d.comment + && !comment.isDitto && !comment.sections.any!(s => s.name == "Returns")) + addErrorMessage(cast(ulong) d.loc.linnum, cast(ulong) d.loc.charnum, + MISSING_RETURNS_KEY, MISSING_RETURNS_MESSAGE); + } + + super.visit(d); + if (nestedFunc == 1) + foreach (t; thrown) + if (!hasThrowSection(to!string(d.comment))) + addErrorMessage(cast(ulong) d.loc.linnum, cast(ulong) d.loc.charnum, + MISSING_THROW_KEY, MISSING_THROW_MESSAGE.format(t)); } - auto setLastDdocParams(Token name, string commentText) + override void visit(AST.TemplateDeclaration d) + { + import dmd.dsymbol : Visibility; + import ddoc.comments : parseComment; + import std.algorithm.iteration : map, filter; + import std.algorithm.searching : find, canFind; + import std.array : array; + + if (d.comment is null) + return; + + // A `template` inside another public `template` declaration will have visibility undefined + // Check that as well as it's part of the public template + if ((d.visibility.kind != Visibility.Kind.public_) + && !(d.visibility.kind == Visibility.Kind.undefined && withinTemplate)) + return; + + if (d.visibility.kind == Visibility.Kind.public_) + { + setLastDdocParams(d.loc.linnum, d.loc.charnum, to!string(d.comment)); + withinTemplate = true; + funcParams.length = 0; + templateParams.length = 0; + } + + foreach (p; *d.origParameters) + if (!canFind(templateParams, to!string(p.ident.toString()))) + templateParams ~= to!string(p.ident.toString()); + + super.visit(d); + + if (d.visibility.kind == Visibility.Kind.public_) + { + withinTemplate = false; + checkDdocParams(d.loc.linnum, d.loc.charnum, funcParams, templateParams); + } + } + + /** + * Look for: foo(T)(T x) + * In that case, T does not have to be documented, because x must be. + */ + override bool visitEponymousMember(AST.TemplateDeclaration d) + { + import ddoc.comments : parseComment; + import std.algorithm.searching : canFind, any, find; + import std.algorithm.iteration : map, filter; + import std.array : array; + + if (!d.members || d.members.length != 1) + return false; + AST.Dsymbol onemember = (*d.members)[0]; + if (onemember.ident != d.ident) + return false; + + if (AST.FuncDeclaration fd = onemember.isFuncDeclaration()) + { + const comment = parseComment(to!string(d.comment), null); + const paramSection = comment.sections.find!(s => s.name == "Params"); + auto tf = fd.type.isTypeFunction(); + + if (tf) + foreach (idx, p; tf.parameterList) + { + + if (!paramSection.empty && + !canFind(paramSection[0].mapping.map!(a => a[0]).array, to!string(p.ident.toString())) && + !canFind(funcParams, to!string(p.ident.toString()))) + funcParams ~= to!string(p.ident.toString()); + + lastSeenFun.params[to!string(p.ident.toString())] = true; + + auto ti = p.type.isTypeIdentifier(); + if (ti is null) + continue; + + templateParams = templateParams.filter!(a => a != to!string(ti.ident.toString())).array; + lastSeenFun.params[to!string(ti.ident.toString())] = true; + } + return true; + } + + if (AST.AggregateDeclaration ad = onemember.isAggregateDeclaration()) + return true; + + if (AST.VarDeclaration vd = onemember.isVarDeclaration()) + { + if (d.constraint) + return false; + + if (vd._init) + return true; + } + + return false; + } + + extern(D) auto setLastDdocParams(size_t line, size_t column, string commentText) { import ddoc.comments : parseComment; import std.algorithm.searching : find; @@ -323,20 +235,28 @@ private: const paramSection = comment.sections.find!(s => s.name == "Params"); if (paramSection.empty) { - lastSeenFun = Function(true, name, null); + lastSeenFun = Function(true, line, column, null); } else { auto ddocParams = paramSection[0].mapping.map!(a => a[0]).array; - lastSeenFun = Function(true, name, ddocParams); + lastSeenFun = Function(true, line, column, ddocParams); } } return comment; } - void checkDdocParams(const Parameters params, - const TemplateParameters templateParameters = null) + /** + * + * Params: + * line = Line of the public declaration verified + * column = Column of the public declaration verified + * params = Funcion parameters that must be documented + * templateParams = Template parameters that must be documented. + * Can be null if we are looking at a regular FuncDeclaration + */ + extern(D) void checkDdocParams(size_t line, size_t column, string[] params, string[] templateParams) { import std.array : array; import std.algorithm.searching : canFind, countUntil; @@ -344,136 +264,73 @@ private: import std.algorithm.mutation : remove; import std.range : indexed, iota; - // convert templateParameters into a string[] for faster access - const(TemplateParameter)[] templateList; - if (const tp = templateParameters) - if (const tpl = tp.templateParameterList) - templateList = tpl.items; - string[] tlList = templateList.map!(a => templateParamName(a).text).array; - - // make a copy of all parameters and remove the seen ones later during the loop - size_t[] unseenTemplates = templateList.length.iota.array; - - if (lastSeenFun.active && params !is null) - foreach (p; params.parameters) + if (lastSeenFun.active && !params.empty) + foreach (p; params) { - string templateName; - 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) - { - unseenTemplates = unseenTemplates.remove(idx); - tlList = tlList.remove(idx); - // documenting template parameter should be allowed - lastSeenFun.params[templateName] = true; - } - - if (!lastSeenFun.ddocParams.canFind(p.name.text)) - addErrorMessage(p.name, MISSING_PARAMS_KEY, - format(MISSING_PARAMS_MESSAGE, p.name.text)); + if (!lastSeenFun.ddocParams.canFind(p)) + addErrorMessage(line, column, MISSING_PARAMS_KEY, + format(MISSING_PARAMS_MESSAGE, p)); else - lastSeenFun.params[p.name.text] = true; + lastSeenFun.params[p] = true; } - // now check the remaining, not used template parameters - auto unseenTemplatesArr = templateList.indexed(unseenTemplates).array; - checkDdocParams(unseenTemplatesArr); + checkDdocParams(line, column, templateParams); } - void checkDdocParams(const TemplateParameters templateParams) - { - if (lastSeenFun.active && templateParams !is null && - templateParams.templateParameterList !is null) - checkDdocParams(templateParams.templateParameterList.items); - } - - void checkDdocParams(const TemplateParameter[] templateParams) + extern(D) void checkDdocParams(size_t line, size_t column, string[] templateParams) { import std.algorithm.searching : canFind; foreach (p; templateParams) { - const name = templateParamName(p); - assert(name !is Token.init, "Invalid template parameter name."); // this shouldn't happen - if (!lastSeenFun.ddocParams.canFind(name.text)) - addErrorMessage(name, MISSING_PARAMS_KEY, - format(MISSING_TEMPLATE_PARAMS_MESSAGE, name.text)); + if (!lastSeenFun.ddocParams.canFind(p)) + addErrorMessage(line, column, MISSING_PARAMS_KEY, + format(MISSING_TEMPLATE_PARAMS_MESSAGE, p)); else - lastSeenFun.params[name.text] = true; + lastSeenFun.params[p] = true; } } - static Token templateParamName(const TemplateParameter p) + extern(D) bool hasThrowSection(string commentText) { - if (p.templateTypeParameter) - return p.templateTypeParameter.identifier; - if (p.templateValueParameter) - return p.templateValueParameter.identifier; - if (p.templateAliasParameter) - return p.templateAliasParameter.identifier; - if (p.templateTupleParameter) - return p.templateTupleParameter.identifier; - if (p.templateThisParameter) - return p.templateThisParameter.templateTypeParameter.identifier; + import std.algorithm.searching : canFind; + import ddoc.comments : parseComment; - return Token.init; + const comment = parseComment(commentText, null); + return comment.isDitto || comment.sections.canFind!(s => s.name == "Throws"); + } + + void postCheckSeenDdocParams() + { + import std.format : format; + + if (lastSeenFun.active) + foreach (p; lastSeenFun.ddocParams) + if (p !in lastSeenFun.params) + addErrorMessage(lastSeenFun.line, lastSeenFun.column, NON_EXISTENT_PARAMS_KEY, + NON_EXISTENT_PARAMS_MESSAGE.format(p)); + + lastSeenFun.active = false; } - bool hasDeclaration(const Declaration decl) + private enum KEY = "dscanner.performance.enum_array_literal"; + int nestedFunc; + int withinTemplate; + + extern(D) string[] funcParams; + extern(D) string[] templateParams; + extern(D) string[] thrown; + + static struct Function { - import std.meta : AliasSeq; - alias properties = AliasSeq!( - "aliasDeclaration", - "aliasThisDeclaration", - "anonymousEnumDeclaration", - "attributeDeclaration", - "classDeclaration", - "conditionalDeclaration", - "constructor", - "debugSpecification", - "destructor", - "enumDeclaration", - "eponymousTemplateDeclaration", - "functionDeclaration", - "importDeclaration", - "interfaceDeclaration", - "invariant_", - "mixinDeclaration", - "mixinTemplateDeclaration", - "postblit", - "pragmaDeclaration", - "sharedStaticConstructor", - "sharedStaticDestructor", - "staticAssertDeclaration", - "staticConstructor", - "staticDestructor", - "structDeclaration", - "templateDeclaration", - "unionDeclaration", - "unittest_", - "variableDeclaration", - "versionSpecification", - ); - if (decl.declarations !is null) - return false; - - auto isNull = true; - foreach (property; properties) - if (mixin("decl." ~ property ~ " !is null")) - isNull = false; - - return !isNull; + bool active; + size_t line, column; + // All params documented + const(string)[] ddocParams; + // Stores actual function params that are also documented + bool[string] params; } + Function lastSeenFun; } version(unittest) @@ -481,7 +338,7 @@ version(unittest) import std.stdio : stderr; import std.format : format; import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; - import dscanner.analysis.helpers : assertAnalyzerWarnings; + import dscanner.analysis.helpers : assertAnalyzerWarnings = assertAnalyzerWarningsDMD; } // missing params @@ -494,73 +351,68 @@ unittest /** Some text */ - void foo(int k){} /+ - ^ [warn]: %s +/ + void foo(int k){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_PARAMS_MESSAGE.format("k") + ), sac, true); assertAnalyzerWarnings(q{ /** Some text */ - void foo(int K)(){} /+ - ^ [warn]: %s +/ + void foo(int K)(){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("K") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("K") + ), sac, true); assertAnalyzerWarnings(q{ /** Some text */ - struct Foo(Bar){} /+ - ^^^ [warn]: %s +/ + struct Foo(Bar){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") + ), sac, true); assertAnalyzerWarnings(q{ /** Some text */ - class Foo(Bar){} /+ - ^^^ [warn]: %s +/ + class Foo(Bar){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") + ), sac, true); assertAnalyzerWarnings(q{ /** Some text */ - template Foo(Bar){} /+ - ^^^ [warn]: %s +/ + template Foo(Bar){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("Bar") + ), sac, true); // test no parameters assertAnalyzerWarnings(q{ /** Some text */ void foo(){} - }c, sac); + }c, sac, true); assertAnalyzerWarnings(q{ /** Some text */ struct Foo(){} - }c, sac); + }c, sac, true); assertAnalyzerWarnings(q{ /** Some text */ class Foo(){} - }c, sac); + }c, sac, true); assertAnalyzerWarnings(q{ /** Some text */ template Foo(){} - }c, sac); + }c, sac, true); } @@ -574,21 +426,19 @@ unittest /** Some text */ - int foo(){} /+ - ^^^ [warn]: %s +/ + int foo(){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_RETURNS_MESSAGE, + ), sac, true); assertAnalyzerWarnings(q{ /** Some text */ - auto foo(){} /+ - ^^^^ [warn]: %s +/ + auto foo(){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_RETURNS_MESSAGE, + ), sac, true); } // ignore private @@ -602,7 +452,7 @@ unittest Some text */ private void foo(int k){} - }c, sac); + }c, sac, true); // with block assertAnalyzerWarnings(q{ @@ -612,16 +462,14 @@ unittest */ private void foo(int k){} /// - public int bar(){} /+ - ^^^ [warn]: %s +/ + public int bar(){ return 0; } // [warn]: %s public: /// - int foobar(){} /+ - ^^^ [warn]: %s +/ + int foobar(){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, - ProperlyDocumentedPublicFunctions.MISSING_RETURNS_MESSAGE, - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_RETURNS_MESSAGE, + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_RETURNS_MESSAGE, + ), sac, true); // with block (template) assertAnalyzerWarnings(q{ @@ -631,16 +479,14 @@ unittest */ private template foo(int k){} /// - public template bar(T){} /+ - ^ [warn]: %s +/ + public template bar(T){} // [warn]: %s public: /// - template foobar(T){} /+ - ^ [warn]: %s +/ + template foobar(T){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), + ), sac, true); // with block (struct) assertAnalyzerWarnings(q{ @@ -650,16 +496,14 @@ unittest */ private struct foo(int k){} /// - public struct bar(T){} /+ - ^ [warn]: %s +/ + public struct bar(T){} // [warn]: %s public: /// - struct foobar(T){} /+ - ^ [warn]: %s +/ + struct foobar(T){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("T"), + ), sac, true); } // test parameter names @@ -677,11 +521,10 @@ unittest * Returns: * A long description. */ -int foo(int k){} /+ - ^ [warn]: %s +/ +int foo(int k){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_PARAMS_MESSAGE.format("k") + ), sac, true); assertAnalyzerWarnings(q{ /** @@ -692,11 +535,10 @@ int foo(int k){} /+ * Returns: * A long description. */ -int foo(int k) => k; /+ - ^ [warn]: %s +/ +int foo(int k) => k; // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_PARAMS_MESSAGE.format("k") + ), sac, true); assertAnalyzerWarnings(q{ /** @@ -709,11 +551,10 @@ k = A stupid parameter Returns: A long description. */ -int foo(int k){} /+ - ^^^ [warn]: %s +/ +int foo(int k){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("val") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).NON_EXISTENT_PARAMS_MESSAGE.format("val") + ), sac, true); assertAnalyzerWarnings(q{ /** @@ -724,11 +565,10 @@ Params: Returns: A long description. */ -int foo(int k){} /+ - ^ [warn]: %s +/ +int foo(int k){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("k") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_PARAMS_MESSAGE.format("k") + ), sac, true); assertAnalyzerWarnings(q{ /** @@ -742,11 +582,10 @@ foobar = A stupid parameter Returns: A long description. */ -int foo(int foo, int foobar){} /+ - ^^^ [warn]: %s +/ +int foo(int foo, int foobar){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).NON_EXISTENT_PARAMS_MESSAGE.format("bad") + ), sac, true); assertAnalyzerWarnings(q{ /** @@ -760,11 +599,10 @@ foobar = A stupid parameter Returns: A long description. */ -struct foo(int foo, int foobar){} /+ - ^^^ [warn]: %s +/ +struct foo(int foo, int foobar){} // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("bad") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).NON_EXISTENT_PARAMS_MESSAGE.format("bad") + ), sac, true); // properly documented assertAnalyzerWarnings(q{ @@ -778,8 +616,8 @@ bar = A stupid parameter Returns: A long description. */ -int foo(int foo, int bar){} - }c, sac); +int foo(int foo, int bar){ return 0; } + }c, sac, true); assertAnalyzerWarnings(q{ /** @@ -793,7 +631,7 @@ Returns: A long description. */ struct foo(int foo, int bar){} - }c, sac); + }c, sac, true); } // support ditto @@ -812,11 +650,11 @@ unittest * Returns: * A long description. */ -int foo(int k){} +int foo(int k){ return 0; } /// ditto -int bar(int k){} - }c, sac); +int bar(int k){ return 0; } + }c, sac, true); assertAnalyzerWarnings(q{ /** @@ -829,11 +667,11 @@ int bar(int k){} * Returns: * A long description. */ -int foo(int k){} +int foo(int k){ return 0; } /// ditto struct Bar(K){} - }c, sac); + }c, sac, true); assertAnalyzerWarnings(q{ /** @@ -846,11 +684,11 @@ struct Bar(K){} * Returns: * A long description. */ -int foo(int k){} +int foo(int k){ return 0; } /// ditto -int bar(int f){} - }c, sac); +int bar(int f){ return 0; } + }c, sac, true); assertAnalyzerWarnings(q{ /** @@ -862,14 +700,13 @@ int bar(int f){} * Returns: * A long description. */ -int foo(int k){} +int foo(int k){ return 0; } /// ditto -int bar(int bar){} /+ - ^^^ [warn]: %s +/ +int bar(int bar){ return 0; } // [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_PARAMS_MESSAGE.format("bar") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_PARAMS_MESSAGE.format("bar") + ), sac, true); assertAnalyzerWarnings(q{ /** @@ -885,14 +722,13 @@ int bar(int bar){} /+ * See_Also: * $(REF takeExactly, std,range) */ -int foo(int k){} /+ - ^^^ [warn]: %s +/ +int foo(int k){ return 0; } // [warn]: %s /// ditto -int bar(int bar){} +int bar(int bar){ return 0; } }c.format( - ProperlyDocumentedPublicFunctions.NON_EXISTENT_PARAMS_MESSAGE.format("f") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).NON_EXISTENT_PARAMS_MESSAGE.format("f") + ), sac, true); } // check correct ddoc headers @@ -912,8 +748,8 @@ unittest Returns: Awesome values. +/ -string bar(string val){} - }c, sac); +string bar(string val){ return ""; } + }c, sac, true); assertAnalyzerWarnings(q{ /++ @@ -927,7 +763,7 @@ string bar(string val){} Returns: Awesome values. +/ template bar(string val){} - }c, sac); + }c, sac, true); } @@ -958,7 +794,7 @@ template abcde(Args ...) { /// .... } } - }c, sac); + }c, sac, true); } // Don't force the documentation of the template parameter if it's a used type in the parameter list @@ -977,7 +813,7 @@ Params: Returns: Awesome values. +/ string bar(R)(R r){} - }c, sac); + }c, sac, true); assertAnalyzerWarnings(q{ /++ @@ -988,11 +824,10 @@ Params: Returns: Awesome values. +/ -string bar(P, R)(R r){}/+ - ^ [warn]: %s +/ +string bar(P, R)(R r){}// [warn]: %s }c.format( - ProperlyDocumentedPublicFunctions.MISSING_TEMPLATE_PARAMS_MESSAGE.format("P") - ), sac); + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_TEMPLATE_PARAMS_MESSAGE.format("P") + ), sac, true); } // https://github.com/dlang-community/D-Scanner/issues/601 @@ -1007,7 +842,7 @@ unittest alias p = put!(Unqual!Range); p(items); } - }, sac); + }, sac, true); } unittest @@ -1026,7 +861,7 @@ unittest +/ void put(Range)(const(Range) items) if (canPutConstRange!Range) {} - }, sac); + }, sac, true); } unittest @@ -1035,214 +870,75 @@ unittest sac.properly_documented_public_functions = Check.enabled; assertAnalyzerWarnings(q{ +class AssertError : Error +{ + this(string msg) { super(msg); } +} + /++ Throw but likely catched. +/ -void bar(){ +void bar1(){ 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); -} + void bar2(){throw new Exception("bla");} // [warn]: %s -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); -} +void bar3(){throw new Exception("bla");} -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); -} +void bar4(){try throw new Exception("bla"); catch(Exception) throw new Error("bla");} // [warn]: %s -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); -} +void bar5() nothrow {try throw new Exception("bla"); catch(Exception) assert(0);} -unittest -{ - StaticAnalysisConfig sac = disabledConfig; - sac.properly_documented_public_functions = Check.enabled; - - assertAnalyzerWarnings(q{ /++ case of throw in nested func +/ -void bar() /+ - ^^^ [warn]: %s +/ +void bar6() // [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 bar7() { 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 bar8() // [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); +}}c.format( + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_THROW_MESSAGE.format("object.Exception"), + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_THROW_MESSAGE.format("object.Error"), + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_THROW_MESSAGE + .format("properly_documented_public_functions.AssertError"), + (ProperlyDocumentedPublicFunctions!ASTCodegen).MISSING_THROW_MESSAGE + .format("properly_documented_public_functions.AssertError") + ), sac, true); } // https://github.com/dlang-community/D-Scanner/issues/583 @@ -1254,10 +950,8 @@ unittest assertAnalyzerWarnings(q{ /++ Implements the homonym function (also known as `accumulate`) - Returns: the accumulated `result` - Params: fun = one or more functions +/ @@ -1266,17 +960,15 @@ unittest { /++ No-seed version. The first element of `r` is used as the seed's value. - Params: r = an iterable value as defined by `isIterable` - Returns: the final result of the accumulator applied to the iterable +/ auto reduce(R)(R r){} } }c.format( - ), sac); + ), sac, true); stderr.writeln("Unittest for ProperlyDocumentedPublicFunctions passed."); } diff --git a/src/dscanner/analysis/run.d b/src/dscanner/analysis/run.d index 3a34a21..c6d3224 100644 --- a/src/dscanner/analysis/run.d +++ b/src/dscanner/analysis/run.d @@ -97,6 +97,9 @@ import dscanner.reports : DScannerJsonReporter, SonarQubeGenericIssueDataReporte import dmd.astbase : ASTBase; import dmd.parse : Parser; +import dmd.frontend; +import dmd.astcodegen; + bool first = true; version (unittest) @@ -393,25 +396,27 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error import dmd.globals : global; import dmd.identifier : Identifier; import std.string : toStringz; - - Id.initialize(); - global._init(); - global.params.useUnitTests = true; - ASTBase.Type._init(); - + import dmd.arraytypes : Strings; bool hasErrors; foreach (fileName; fileNames) { - auto code = readFile(fileName); - auto id = Identifier.idPool(fileName); - auto ast_m = new ASTBase.Module(fileName.toStringz, id, false, false); + auto dmdParentDir = dirName(dirName(dirName(dirName(__FILE_FULL_PATH__)))); + + global.params.useUnitTests = true; + global.path = new Strings(); + global.path.push((dmdParentDir ~ "/dmd").ptr); + global.path.push((dmdParentDir ~ "/dmd/druntime/src").ptr); + + initDMD(); + + auto code = readFile(fileName); auto input = cast(char[]) code; input ~= '\0'; - scope astbaseParser = new Parser!ASTBase(ast_m, input, false); - astbaseParser.nextToken(); - ast_m.members = astbaseParser.parseModule(); + + auto t = dmd.frontend.parseModule(cast(const(char)[]) fileName, cast(const (char)[]) input); + // t.module_.fullSemantic(); // Skip files that could not be read and continue with the rest if (code.length == 0) @@ -426,7 +431,7 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error if (errorCount > 0 || (staticAnalyze && warningCount > 0)) hasErrors = true; MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze); - MessageSet resultsDmd = analyzeDmd(fileName, ast_m, getModuleName(astbaseParser.md), config); + MessageSet resultsDmd = analyzeDmd(fileName, t.module_, getModuleName(t.module_.md), config); foreach (result; resultsDmd[]) { results.insert(result); @@ -739,7 +744,7 @@ bool shouldRun(check : BaseAnalyzer)(string moduleName, const ref StaticAnalysis * * If no includes are specified, all modules are included. */ -bool shouldRunDmd(check : BaseAnalyzerDmd!ASTBase)(const char[] moduleName, const ref StaticAnalysisConfig config) +bool shouldRunDmd(check : BaseAnalyzerDmd)(const char[] moduleName, const ref StaticAnalysisConfig config) { enum string a = check.name; @@ -921,10 +926,6 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName, checks ~= new AutoFunctionChecker(args.setSkipTests( analysisConfig.auto_function_check == Check.skipTests && !ut)); - if (moduleName.shouldRun!ProperlyDocumentedPublicFunctions(analysisConfig)) - checks ~= new ProperlyDocumentedPublicFunctions(args.setSkipTests( - analysisConfig.properly_documented_public_functions == Check.skipTests && !ut)); - if (moduleName.shouldRun!VcallCtorChecker(analysisConfig)) checks ~= new VcallCtorChecker(args.setSkipTests( analysisConfig.vcall_in_ctor == Check.skipTests && !ut)); @@ -1265,77 +1266,83 @@ version (unittest) } } -MessageSet analyzeDmd(string fileName, ASTBase.Module m, const char[] moduleName, const StaticAnalysisConfig config) +MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleName, const StaticAnalysisConfig config) { MessageSet set = new MessageSet; - BaseAnalyzerDmd!ASTBase[] visitors; + BaseAnalyzerDmd[] visitors; - if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTBase)(config)) - visitors ~= new ObjectConstCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTCodegen)(config)) + visitors ~= new ObjectConstCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTBase)(config)) - visitors ~= new EnumArrayVisitor!ASTBase(fileName); + if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTCodegen)(config)) + visitors ~= new EnumArrayVisitor!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(DeleteCheck!ASTBase)(config)) - visitors ~= new DeleteCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(DeleteCheck!ASTCodegen)(config)) + visitors ~= new DeleteCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTBase)(config)) - visitors ~= new FinalAttributeChecker!ASTBase(fileName); + if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTCodegen)(config)) + visitors ~= new FinalAttributeChecker!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTBase)(config)) - visitors ~= new ImportSortednessCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTCodegen)(config)) + visitors ~= new ImportSortednessCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTBase)(config)) - visitors ~= new IncorrectInfiniteRangeCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTCodegen)(config)) + visitors ~= new IncorrectInfiniteRangeCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTBase)(config)) - visitors ~= new RedundantAttributesCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTCodegen)(config)) + visitors ~= new RedundantAttributesCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTBase)(config)) - visitors ~= new LengthSubtractionCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTCodegen)(config)) + visitors ~= new LengthSubtractionCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTBase)(config)) - visitors ~= new AliasSyntaxCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTCodegen)(config)) + visitors ~= new AliasSyntaxCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTBase)(config)) - visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTCodegen)(config)) + visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(ConstructorCheck!ASTBase)(config)) - visitors ~= new ConstructorCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(ConstructorCheck!ASTCodegen)(config)) + visitors ~= new ConstructorCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTBase)(config)) - visitors ~= new AssertWithoutMessageCheck!ASTBase( + if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTCodegen)(config)) + visitors ~= new AssertWithoutMessageCheck!ASTCodegen( fileName, config.assert_without_msg == Check.skipTests && !ut ); - if (moduleName.shouldRunDmd!(LocalImportCheck!ASTBase)(config)) - visitors ~= new LocalImportCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(LocalImportCheck!ASTCodegen)(config)) + visitors ~= new LocalImportCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTBase)(config)) - visitors ~= new OpEqualsWithoutToHashCheck!ASTBase( + if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTCodegen)(config)) + visitors ~= new OpEqualsWithoutToHashCheck!ASTCodegen( fileName, config.opequals_tohash_check == Check.skipTests && !ut ); - if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTBase)(config)) - visitors ~= new AutoRefAssignmentCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTCodegen)(config)) + visitors ~= new AutoRefAssignmentCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTBase)(config)) - visitors ~= new LogicPrecedenceCheck!ASTBase( + if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTCodegen)(config)) + visitors ~= new LogicPrecedenceCheck!ASTCodegen( fileName, config.logical_precedence_check == Check.skipTests && !ut ); - if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTBase)(config)) - visitors ~= new BuiltinPropertyNameCheck!ASTBase(fileName); + if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config)) + visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName); - if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTBase)(config)) - visitors ~= new BackwardsRangeCheck!ASTBase( + if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTCodegen)(config)) + visitors ~= new BackwardsRangeCheck!ASTCodegen( fileName, config.backwards_range_check == Check.skipTests && !ut ); + if (moduleName.shouldRunDmd!(ProperlyDocumentedPublicFunctions!ASTCodegen)(config)) + visitors ~= new ProperlyDocumentedPublicFunctions!ASTCodegen( + fileName, + config.properly_documented_public_functions == Check.skipTests && !ut + ); + foreach (visitor; visitors) { m.accept(visitor); diff --git a/src/dscanner/imports.d b/src/dscanner/imports.d index e58a8db..072eba8 100644 --- a/src/dscanner/imports.d +++ b/src/dscanner/imports.d @@ -50,6 +50,8 @@ extern(C++) class ImportVisitor(AST) : ParseTimeTransitiveVisitor!AST private void visitFile(bool usingStdin, string fileName, RedBlackTree!string importedModules) { + import dmd.errorsink : ErrorSinkNull; + Id.initialize(); global._init(); global.params.useUnitTests = true; @@ -60,7 +62,11 @@ private void visitFile(bool usingStdin, string fileName, RedBlackTree!string imp ubyte[] bytes = usingStdin ? readStdin() : readFile(fileName); auto input = cast(char[]) bytes; - scope p = new Parser!ASTBase(m, input, false); + __gshared ErrorSinkNull errorSinkNull; + if (!errorSinkNull) + errorSinkNull = new ErrorSinkNull; + + scope p = new Parser!ASTBase(m, input, false, new ErrorSinkNull, null, false); p.nextToken(); m.members = p.parseModule(); diff --git a/src/dscanner/utils.d b/src/dscanner/utils.d index d08a41e..47b1ebc 100644 --- a/src/dscanner/utils.d +++ b/src/dscanner/utils.d @@ -10,6 +10,7 @@ import std.path: isValidPath; import dmd.astbase : ASTBase; import dmd.parse : Parser; +import dmd.astcodegen; private void processBOM(ref ubyte[] sourceCode, string fname) { @@ -316,14 +317,14 @@ auto ref safeAccess(M)(M m) /** * Return the module name from a ModuleDeclaration instance with the following format: `foo.bar.module` */ -const(char[]) getModuleName(ASTBase.ModuleDeclaration *mdptr) +const(char[]) getModuleName(ASTCodegen.ModuleDeclaration *mdptr) { import std.array : array, join; if (mdptr !is null) { import std.algorithm : map; - ASTBase.ModuleDeclaration md = *mdptr; + ASTCodegen.ModuleDeclaration md = *mdptr; if (md.packages.length != 0) return join(md.packages.map!(e => e.toString()).array ~ md.id.toString().dup, ".");