diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java index c50aeef8a..a30647352 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/GetDefinition.java @@ -6,6 +6,8 @@ import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstscript.WLogger; import de.peeeq.wurstscript.ast.*; +import de.peeeq.wurstscript.attributes.CofigOverridePackages; +import de.peeeq.wurstscript.attributes.names.NameLink; import de.peeeq.wurstscript.parser.WPos; import de.peeeq.wurstscript.utils.Utils; import org.eclipse.lsp4j.Location; @@ -45,6 +47,13 @@ private List execute2(ModelManager modelManager) { } Element e = Utils.getAstElementAtPos(cu, line, column, false).get(); WLogger.info("get definition at: " + e.getClass().getSimpleName()); + NameDef configuredDecl = getConfiguredDeclarationAtPos(e); + if (configuredDecl != null) { + NameDef originalDecl = getOriginalConfigDeclaration(configuredDecl); + if (originalDecl != null) { + return linkTo(originalDecl); + } + } if (e instanceof FuncRef) { FuncRef funcRef = (FuncRef) e; FunctionDefinition decl = funcRef.attrFuncDef(); @@ -85,6 +94,40 @@ private List execute2(ModelManager modelManager) { return Collections.emptyList(); } + private NameDef getConfiguredDeclarationAtPos(Element e) { + if (e instanceof NameDef) { + return (NameDef) e; + } + Element current = e; + while (current != null) { + if (current instanceof NameDef) { + return (NameDef) current; + } + current = current.getParent(); + } + return null; + } + + private NameDef getOriginalConfigDeclaration(NameDef nameDef) { + if (!(nameDef instanceof GlobalVarDef) || !nameDef.hasAnnotation("@config")) { + return null; + } + PackageOrGlobal nearestPackage = nameDef.attrNearestPackage(); + if (!(nearestPackage instanceof WPackage)) { + return null; + } + WPackage configPackage = (WPackage) nearestPackage; + if (!configPackage.getName().endsWith(CofigOverridePackages.CONFIG_POSTFIX)) { + return null; + } + WPackage originalPackage = CofigOverridePackages.getOriginalPackage(configPackage); + if (originalPackage == null) { + return null; + } + NameLink originalVar = originalPackage.getElements().lookupVarNoConfig(nameDef.getName(), false); + return originalVar == null ? null : originalVar.getDef(); + } + private List linkTo(AstElementWithSource decl) { if (decl == null) { return Collections.emptyList(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java index 9b3bb474a..e952134f4 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java @@ -5,8 +5,10 @@ import de.peeeq.wurstio.languageserver.WFile; import de.peeeq.wurstscript.WLogger; import de.peeeq.wurstscript.ast.*; +import de.peeeq.wurstscript.attributes.AttrWurstDoc; import de.peeeq.wurstscript.attributes.names.FuncLink; import de.peeeq.wurstscript.attributes.names.NameLink; +import de.peeeq.wurstscript.parser.TriviaIndex; import de.peeeq.wurstscript.types.WurstType; import de.peeeq.wurstscript.types.WurstTypeNamedScope; import de.peeeq.wurstscript.utils.Utils; @@ -18,6 +20,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * Created by peter on 24.04.16. @@ -43,6 +46,11 @@ public Hover execute(ModelManager modelManager) { if (cu == null) { return new Hover(Collections.singletonList(Either.forLeft("File " + filename + " is not part of the project. Move it to the wurst folder."))); } + int offset = offsetAt(line, column); + Optional commentTrivia = cu.getCuInfo().getTriviaIndex().findCommentAtOffset(offset); + if (commentTrivia.isPresent()) { + return new Hover(Collections.emptyList()); + } Element e = Utils.getAstElementAtPos(cu, line, column, false).get(); WLogger.debug("hovering over " + Utils.printElement(e)); List> desription = e.match(new Description()); @@ -51,6 +59,24 @@ public Hover execute(ModelManager modelManager) { return new Hover(desription); } + private int offsetAt(int line, int column) { + int currentLine = 1; + int currentColumn = 1; + for (int i = 0; i < buffer.length(); i++) { + if (currentLine == line && currentColumn == column) { + return i; + } + char c = buffer.charAt(i); + if (c == '\n') { + currentLine++; + currentColumn = 1; + } else { + currentColumn++; + } + } + return Math.max(0, buffer.length() - 1); + } + private List> addArgumentHint(Element e, List> desription) { try { if (e.getParent() instanceof Arguments) { @@ -62,7 +88,7 @@ private List> addArgumentHint(Element e, List> description(NameDef n) { if (comment != null && !comment.isEmpty()) { result.add(Either.forLeft(comment)); } - if (n.attrIsConstant()) { - if (n instanceof GlobalOrLocalVarDef) { - GlobalOrLocalVarDef v = (GlobalOrLocalVarDef) n; - VarInitialization initialExpr = v.getInitialExpr(); - String initial = Utils.prettyPrint(initialExpr); - result.add(Either.forRight(new MarkedString("wurst", " = " + initial))); + + String initializer = ""; + if (n instanceof GlobalOrLocalVarDef) { + GlobalOrLocalVarDef v = (GlobalOrLocalVarDef) n; + VarInitialization initialExpr = v.getInitialExpr(); + if (!(initialExpr instanceof NoExpr)) { + initializer = " = " + Utils.prettyPrint(initialExpr); } } - String additionalProposalInfo = type(n.attrTyp()) + " " + n.getName() - + " defined in " + nearestScopeName(n); - result.add(Either.forLeft(additionalProposalInfo)); + if (n instanceof TypeParamDef) { + result.add(Either.forRight(new MarkedString("wurst", "type parameter " + n.getName()))); + } else { + result.add(Either.forRight(new MarkedString("wurst", type(n.attrTyp()) + " " + n.getName() + initializer))); + } + result.add(Either.forLeft("defined in " + nearestScopeName(n))); return result; } @@ -266,10 +296,13 @@ public List> case_ExprCast(ExprCast e) { @Override public List> case_WImport(WImport imp) { + List> result = new ArrayList<>(); WPackage imported = imp.attrImportedPackage(); - if (imported != null) - return string(imported.attrComment()); - return string("import ..."); + if (imported != null && imported.attrComment() != null && !imported.attrComment().isEmpty()) { + result.add(Either.forLeft(imported.attrComment())); + } + result.add(Either.forRight(new MarkedString("wurst", "import " + imp.getPackagename()))); + return result; } @Override @@ -303,31 +336,22 @@ public List> case_Annotation(Annotation annotation) @Override public List> case_StmtExitwhen(StmtExitwhen stmtExitwhen) { - return string("extiwhen: Exits the current loop when the condition is true"); + return string("exitwhen: exits the current loop when the condition is true."); } @Override public List> case_ConstructorDef(ConstructorDef constr) { - List> result = new ArrayList<>(); - NamedScope c = constr.attrNearestNamedScope(); - String comment = constr.attrComment(); - result.add(Either.forLeft(comment)); - - - String descr = "construct(" + getParameterString(constr) + ") " - + "defined in " + Utils.printElement(c); - result.add(Either.forRight(new MarkedString("wurst", descr))); - return result; + return description(constr); } @Override public List> case_WImports(WImports wImports) { - return string("imports"); + return Collections.emptyList(); } @Override public List> case_WStatements(WStatements wStatements) { - return string("statements"); + return Collections.emptyList(); } @Override @@ -337,7 +361,7 @@ public List> case_CompilationUnit(CompilationUnit c @Override public List> case_SwitchStmt(SwitchStmt switchStmt) { - return string("A switch statement does different things depending on the value of an epxression."); + return string("A switch statement executes branches based on an expression value."); } @Override @@ -373,7 +397,7 @@ public List> case_SomeSuperConstructorCall(SomeSupe @Override public List> case_LocalVarDef(LocalVarDef v) { - return string("Local Variable " + v.getName() + " of type " + type(v.attrTyp())); + return description(v); } private List> string(String s) { @@ -467,12 +491,12 @@ public List> case_ExprTypeId(ExprTypeId exprTypeId) @Override public List> case_TypeExprList(TypeExprList typeExprList) { - return string("A list of type-expressions"); + return Collections.emptyList(); } @Override public List> case_FuncDefs(FuncDefs funcDefs) { - return string("A list of function definitions"); + return Collections.emptyList(); } @Override @@ -496,22 +520,22 @@ public List> case_VisibilityDefault(VisibilityDefau @Override public List> case_Arguments(Arguments arguments) { - return string("List of arguments"); + return Collections.emptyList(); } @Override public List> case_ModuleInstanciations(ModuleInstanciations moduleInstanciations) { - return string("List of module instantiations."); + return Collections.emptyList(); } @Override public List> case_WShortParameters(WShortParameters wShortParameters) { - return string("Parameters of anonymous function."); + return Collections.emptyList(); } @Override public List> case_SwitchDefaultCaseStatements(SwitchDefaultCaseStatements switchDefaultCaseStatements) { - return string("Default statements of switch-statement"); + return Collections.emptyList(); } @Override @@ -521,7 +545,7 @@ public List> case_ExprStatementsBlock(ExprStatement @Override public List> case_ModuleUses(ModuleUses moduleUses) { - return string("A list of module uses"); + return Collections.emptyList(); } @Override @@ -531,7 +555,7 @@ public List> case_GlobalVarDef(GlobalVarDef globalV @Override public List> case_JassToplevelDeclarations(JassToplevelDeclarations jassToplevelDeclarations) { - return string("A list of declarations."); + return Collections.emptyList(); } @Override @@ -546,19 +570,23 @@ public List> case_ExprMemberArrayVarDotDot(ExprMemb @Override public List> case_ConstructorDefs(ConstructorDefs constructorDefs) { - return string("A list of constructors"); + return Collections.emptyList(); } private List> typeExpr(TypeExpr t) { + NameDef nameDef = t.tryGetNameDef(); + if (nameDef != null) { + return description(nameDef); + } WurstType wt = t.attrTyp(); if (wt == null) { - return string("Type " + t); + return Collections.singletonList(Either.forRight(new MarkedString("wurst", "type " + t))); } if (wt instanceof WurstTypeNamedScope) { WurstTypeNamedScope wtn = (WurstTypeNamedScope) wt; return description(wtn.getDef()); } - return string(type(wt)); + return Collections.singletonList(Either.forRight(new MarkedString("wurst", type(wt)))); } @Override @@ -574,7 +602,7 @@ public List> case_TypeExprSimple(TypeExprSimple t) @Override public List> case_Modifiers(Modifiers modifiers) { - return string("A list of modifiers"); + return string("Modifiers for this declaration."); } @Override @@ -599,7 +627,7 @@ public List> case_FuncDef(FuncDef funcDef) { @Override public List> case_ExprList(ExprList exprList) { - return string("A list of expressions."); + return Collections.emptyList(); } @Override @@ -659,17 +687,17 @@ public List> case_ExprFuncRef(ExprFuncRef exprFuncR @Override public List> case_TypeParamDefs(TypeParamDefs typeParamDefs) { - return string("A list of type parameters."); + return Collections.emptyList(); } @Override public List> case_StmtForFrom(StmtForFrom stmtForFrom) { - return string("The for-from loop takes an iterate and takes elements from the iterator until it is empty."); + return string("The for-from loop repeatedly takes elements from an iterator until it is empty."); } @Override public List> case_Indexes(Indexes indexes) { - return string("A list of indexes"); + return Collections.emptyList(); } @Override @@ -719,7 +747,7 @@ public List> case_IdentifierWithTypeParamDefs(Ident @Override public List> case_GlobalVarDefs(GlobalVarDefs globalVarDefs) { - return string("A list of global variables."); + return Collections.emptyList(); } @Override @@ -734,7 +762,7 @@ public List> case_StmtReturn(StmtReturn stmtReturn) @Override public List> case_WPackages(WPackages wPackages) { - return string("A list of packages."); + return Collections.emptyList(); } @Override @@ -744,7 +772,7 @@ public List> case_ExprIfElse(ExprIfElse exprIfElse) @Override public List> case_WurstDoc(WurstDoc wurstDoc) { - return wurstDoc.getParent().match(this); + return string(AttrWurstDoc.normalizeHotdocComment(wurstDoc.getRawComment())); } @Override @@ -769,7 +797,7 @@ public List> case_OnDestroyDef(OnDestroyDef onDestr @Override public List> case_ModVararg(ModVararg modVararg) { - return string("Declares the parameter to be a array of variable length"); + return string("Declares this parameter as a variable-length argument list."); } @Override @@ -784,7 +812,7 @@ public List> case_VisibilityPublic(VisibilityPublic @Override public List> case_TopLevelDeclarations(TopLevelDeclarations topLevelDeclarations) { - return string("A list of declarations."); + return Collections.emptyList(); } @Override @@ -799,12 +827,12 @@ public List> case_ExprDestroy(ExprDestroy exprDestr @Override public List> case_WEntities(WEntities wEntities) { - return string("A list of entities"); + return Collections.emptyList(); } @Override public List> case_ArraySizes(ArraySizes arraySizes) { - return string("A list of array-sizes"); + return Collections.emptyList(); } @Override @@ -819,12 +847,15 @@ public List> case_SwitchCase(SwitchCase switchCase) @Override public List> case_EnumMembers(EnumMembers enumMembers) { - return string("A list of enum-members."); + return Collections.emptyList(); } @Override public List> case_TypeExprThis(TypeExprThis typeExprThis) { - return typeExpr(typeExprThis); + List> result = new ArrayList<>(); + result.add(Either.forLeft("'thistype' refers to the dynamic type of 'this' in the current context.")); + result.add(Either.forRight(new MarkedString("wurst", "resolved as " + type(typeExprThis.attrTyp())))); + return result; } @Override @@ -864,7 +895,7 @@ public List> case_ExprMemberVarDot(ExprMemberVarDot @Override public List> case_WParameters(WParameters wParameters) { - return string("A list of parameters"); + return Collections.emptyList(); } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java index 23cab0c2c..2f7ae6fff 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/WurstParser.java @@ -14,6 +14,7 @@ import de.peeeq.wurstscript.jurst.ExtendedJurstLexer; import de.peeeq.wurstscript.jurst.antlr.JurstParser; import de.peeeq.wurstscript.parser.AntlrTokenPipeline; +import de.peeeq.wurstscript.parser.TriviaIndex; import de.peeeq.wurstscript.parser.WurstAntlrErrorListener; import de.peeeq.wurstscript.parser.antlr.AntlrWurstParseTreeTransformer; import de.peeeq.wurstscript.parser.antlr.ExtendedWurstLexer; @@ -21,6 +22,8 @@ import java.io.IOException; import java.io.Reader; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.regex.Pattern; public class WurstParser { @@ -92,11 +95,12 @@ private CompilationUnit parseWithAntlr(Reader reader, final String source, boole gui.sendError(warning); } + Deque commentTokens = new ArrayDeque<>(lexerRef[0].getCommentTokens()); CompilationUnit root = new AntlrWurstParseTreeTransformer( source, errorHandler, lexerRef[0].getLineOffsets(), - lexerRef[0].getCommentTokens(), + new ArrayDeque<>(commentTokens), true ).transform(res.parseTree); @@ -104,6 +108,7 @@ private CompilationUnit parseWithAntlr(Reader reader, final String source, boole removeSyntacticSugar(root, hasCommonJ); } root.getCuInfo().setIndentationMode(lexerRef[0].getIndentationMode()); + root.getCuInfo().setTriviaIndex(TriviaIndex.fromTokens(res.tokens.getTokens(), commentTokens)); return root; } catch (IOException e) { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrWurstDoc.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrWurstDoc.java index e4e39f4ca..c9d60b7ed 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrWurstDoc.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrWurstDoc.java @@ -6,6 +6,10 @@ public class AttrWurstDoc { public static String getComment(NameDef nameDef) { + String triviaComment = getCommentFromTrivia(nameDef); + if (!triviaComment.isEmpty()) { + return triviaComment; + } if (nameDef instanceof HasModifier) { HasModifier HasModifier = nameDef; return getCommmentHelper(HasModifier); @@ -26,12 +30,28 @@ private static String getCommmentHelper( } public static String getComment(ConstructorDef constructorDef) { + String triviaComment = getCommentFromTrivia(constructorDef); + if (!triviaComment.isEmpty()) { + return triviaComment; + } return getCommmentHelper(constructorDef); } + private static String getCommentFromTrivia(AstElementWithSource e) { + CompilationUnit cu = e.attrCompilationUnit(); + if (cu == null || cu.getCuInfo() == null) { + return ""; + } + return cu.getCuInfo().getTriviaIndex().findLeadingHotdoc(e.getSource().getLeftPos()); + } + private static String comment(WurstDoc wurstDoc) { - String result = wurstDoc.getRawComment(); + return normalizeHotdocComment(wurstDoc.getRawComment()); + } + + public static String normalizeHotdocComment(String rawComment) { + String result = rawComment; if (result.length() <= 5) { return ""; } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/CompilationUnitInfo.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/CompilationUnitInfo.java index 237df14cb..4490c5764 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/CompilationUnitInfo.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/CompilationUnitInfo.java @@ -1,5 +1,6 @@ package de.peeeq.wurstscript.attributes; +import de.peeeq.wurstscript.parser.TriviaIndex; import de.peeeq.wurstscript.utils.Utils; /** @@ -9,6 +10,7 @@ public class CompilationUnitInfo { private String file = ""; private de.peeeq.wurstscript.attributes.ErrorHandler cuErrorHandler; private IndentationMode indentationMode = IndentationMode.spaces(4); + private TriviaIndex triviaIndex = TriviaIndex.empty(); public CompilationUnitInfo(ErrorHandler cuErrorHandler) { this.cuErrorHandler = cuErrorHandler; @@ -38,6 +40,14 @@ public void setIndentationMode(IndentationMode indentationMode) { this.indentationMode = indentationMode; } + public TriviaIndex getTriviaIndex() { + return triviaIndex; + } + + public void setTriviaIndex(TriviaIndex triviaIndex) { + this.triviaIndex = triviaIndex == null ? TriviaIndex.empty() : triviaIndex; + } + public interface IndentationMode { static IndentationMode tabs() { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/TriviaIndex.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/TriviaIndex.java new file mode 100644 index 000000000..225a1202c --- /dev/null +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/parser/TriviaIndex.java @@ -0,0 +1,131 @@ +package de.peeeq.wurstscript.parser; + +import de.peeeq.wurstscript.antlr.WurstParser; +import de.peeeq.wurstscript.attributes.AttrWurstDoc; +import org.antlr.v4.runtime.Token; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.Optional; + +public final class TriviaIndex { + + private static final TriviaIndex EMPTY = new TriviaIndex(Collections.emptyList()); + + private final List tokens; + + private TriviaIndex(List tokens) { + this.tokens = tokens; + } + + public static TriviaIndex empty() { + return EMPTY; + } + + public static TriviaIndex fromTokens(List tokens) { + if (tokens == null || tokens.isEmpty()) { + return EMPTY; + } + return new TriviaIndex(new ArrayList<>(tokens)); + } + + public static TriviaIndex fromTokens(List tokens, Deque hiddenCommentTokens) { + if ((tokens == null || tokens.isEmpty()) && (hiddenCommentTokens == null || hiddenCommentTokens.isEmpty())) { + return EMPTY; + } + List allTokens = new ArrayList<>(); + if (tokens != null) { + allTokens.addAll(tokens); + } + if (hiddenCommentTokens != null) { + allTokens.addAll(hiddenCommentTokens); + } + allTokens.sort((a, b) -> { + int byStart = Integer.compare(a.getStartIndex(), b.getStartIndex()); + if (byStart != 0) { + return byStart; + } + return Integer.compare(a.getStopIndex(), b.getStopIndex()); + }); + return new TriviaIndex(allTokens); + } + + public String findLeadingHotdoc(int declarationStartOffset) { + int targetTokenIndex = firstNonTriviaTokenAtOrAfter(declarationStartOffset); + if (targetTokenIndex < 0) { + return ""; + } + for (int i = targetTokenIndex - 1; i >= 0; i--) { + Token token = tokens.get(i); + int type = token.getType(); + if (type == WurstParser.HOTDOC_COMMENT) { + return AttrWurstDoc.normalizeHotdocComment(token.getText()); + } + if (!isTriviaBetweenDocAndDeclaration(token)) { + return ""; + } + } + return ""; + } + + public Optional findCommentAtOffset(int offset) { + for (Token token : tokens) { + if (token.getStartIndex() <= offset && offset <= token.getStopIndex() && isCommentToken(token.getType())) { + return Optional.of(new CommentTrivia(token.getType(), token.getText())); + } + } + return Optional.empty(); + } + + private int firstNonTriviaTokenAtOrAfter(int offset) { + for (int i = 0; i < tokens.size(); i++) { + Token token = tokens.get(i); + if (token.getStartIndex() < offset) { + continue; + } + if (!isTriviaBetweenDocAndDeclaration(token)) { + return i; + } + } + return -1; + } + + private boolean isTriviaBetweenDocAndDeclaration(Token token) { + int type = token.getType(); + if (type == WurstParser.HOTDOC_COMMENT + || type == WurstParser.LINE_COMMENT + || type == WurstParser.ML_COMMENT + || type == WurstParser.NL + || type == WurstParser.TAB + || type == WurstParser.SPACETAB) { + return true; + } + return token.getChannel() != Token.DEFAULT_CHANNEL; + } + + private boolean isCommentToken(int type) { + return type == WurstParser.HOTDOC_COMMENT + || type == WurstParser.LINE_COMMENT + || type == WurstParser.ML_COMMENT; + } + + public static final class CommentTrivia { + private final int tokenType; + private final String rawText; + + public CommentTrivia(int tokenType, String rawText) { + this.tokenType = tokenType; + this.rawText = rawText; + } + + public int getTokenType() { + return tokenType; + } + + public String getRawText() { + return rawText; + } + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/AutoCompleteTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/AutoCompleteTests.java index 41eb58c7c..137a9166e 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/AutoCompleteTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/AutoCompleteTests.java @@ -451,6 +451,29 @@ public void nonConstantArrayDoesNotSuggestLength() { ); } + @Test + public void completionUsesHotdocComment() { + CompletionTestData testData = input( + "package test", + "/** docs for foo */", + "function fooBar()", + "init", + " foo|", + "endpackage" + ); + + CompletionList completions = calculateCompletions(testData); + CompletionItem completion = completions.getItems().stream() + .filter(c -> "fooBar".equals(c.getLabel())) + .findFirst() + .orElseThrow(() -> new AssertionError("fooBar completion not found")); + + assertTrue( + completion.getDocumentation() != null && completion.getDocumentation().getLeft().contains("docs for foo"), + "completion documentation = " + completion.getDocumentation() + ); + } + private void testCompletions(CompletionTestData testData, String... expectedCompletions) { testCompletions(testData, Arrays.asList(expectedCompletions)); } diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java index 49d428604..7b0dd0973 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/GetDefinitionTests.java @@ -67,6 +67,34 @@ public void nonexistantModule() { testGetDef(testData, Collections.emptyList()); } + @Test + public void configVarDefinitionJumpsToConfigurableVar() { + CompletionTestData testData = input( + "package SoundUtils", + " @configurable int DEFAULT_SOUND_VOLUME = 127", + "endpackage", + "package SoundUtils_config", + " @config int DEFAULT_SOUND_VOLUME| = 42", + "endpackage" + ); + + testGetDef(testData, "1:22-1:42"); + } + + @Test + public void configAnnotationJumpsToConfigurableVar() { + CompletionTestData testData = input( + "package SoundUtils", + " @configurable int DEFAULT_SOUND_VOLUME = 127", + "endpackage", + "package SoundUtils_config", + " @co|nfig int DEFAULT_SOUND_VOLUME = 42", + "endpackage" + ); + + testGetDef(testData, "1:22-1:42"); + } + private void testGetDef(CompletionTestData testData, String... expectedPositions) { testGetDef(testData, Arrays.asList(expectedPositions)); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/HoverTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/HoverTests.java index 09fee9366..e5a1f5c3b 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/HoverTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/HoverTests.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; /** * tests the get definition functionality. @@ -35,6 +36,104 @@ public void nonexistantModule() { assertEquals(Collections.emptyList(), text); } + @Test + public void hoverUsesHotdocComment() { + CompletionTestData testData = input( + "package test", + "/** this is hover doc */", + "function fo|o()", + "endpackage" + ); + + List text = testHoverText(testData); + assertTrue(text.stream().anyMatch(s -> s.contains("this is hover doc")), "hover text = " + text); + } + + @Test + public void hoverOnCommentShowsNothing() { + CompletionTestData testData = input( + "package test", + "// regular comment |text", + "function foo()", + "endpackage" + ); + + List text = testHoverText(testData); + assertEquals(text, Collections.emptyList(), "hover text = " + text); + } + + @Test + public void hoverOnThisTypeShowsKeywordExplanation() { + CompletionTestData testData = input( + "package test", + "/** class doc which should not appear for thistype */", + "class C", + " function f() returns th|istype", + " return this", + "endpackage" + ); + + List text = testHoverText(testData); + assertTrue(text.stream().anyMatch(s -> s.contains("dynamic type of 'this'")), "hover text = " + text); + assertTrue(text.stream().noneMatch(s -> s.contains("class doc which should not appear")), "hover text = " + text); + } + + @Test + public void hoverForTypeParameterIsReadable() { + CompletionTestData testData = input( + "package test", + "class Box", + " function f(T| value)", + "endpackage" + ); + + List text = testHoverText(testData); + assertTrue(text.stream().anyMatch(s -> s.contains("type parameter T")), "hover text = " + text); + } + + @Test + public void hoverForGlobalShowsInlineInitializer() { + CompletionTestData testData = input( + "package test", + "constant int DEFAULT_SOUND_VOLUME = 127", + "init", + " DEFAULT_SOUND_V|OLUME", + "endpackage" + ); + + List text = testHoverText(testData); + assertTrue(text.stream().anyMatch(s -> s.contains("int DEFAULT_SOUND_VOLUME = 127")), "hover text = " + text); + assertTrue(text.stream().noneMatch(s -> s.startsWith(" = ")), "hover text = " + text); + } + + @Test + public void hoverForLocalVarIsStyledSignature() { + CompletionTestData testData = input( + "package test", + "function f()", + " int tem|pPos = 42", + "endpackage" + ); + + List text = testHoverText(testData); + assertTrue(text.stream().anyMatch(s -> s.contains("int tempPos = 42")), "hover text = " + text); + assertTrue(text.stream().noneMatch(s -> s.contains("Local Variable")), "hover text = " + text); + } + + @Test + public void hoverOnImportShowsImportSignature() { + CompletionTestData testData = input( + "package source", + "endpackage", + "package target", + "import so|urce", + "endpackage" + ); + + List text = testHoverText(testData); + assertTrue(text.stream().anyMatch(s -> s.contains("import source")), "hover text = " + text); + } + private List testHoverText(CompletionTestData testData) { Hover result = getHoverInfo(testData); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTypecastingTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTypecastingTests.java index 723157238..14094fdb7 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTypecastingTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTypecastingTests.java @@ -188,5 +188,15 @@ public void compiletimeNull3() { ); } -} + @Test + public void compiletimeHashMapStringPutInLua() { + test().testLua(true).withStdLib().lines( + "package Test", + "import HashMap", + "let map = compiletime(new HashMap())", + "@compiletime function initialize()", + " map.put(\"hello\", \"world\")" + ); + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java index 099d8baec..c1a79ec49 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java @@ -246,6 +246,67 @@ public void renamePackage() throws IOException { } + @Test + public void unresolvedImportsAreAllReported() throws IOException { + File projectFolder = new File("./temp/testProject_all_import_errors/"); + File wurstFolder = new File(projectFolder, "wurst"); + newCleanFolder(wurstFolder); + + WFile fileMainA = WFile.create(new File(wurstFolder, "MainA.wurst")); + WFile fileMainB = WFile.create(new File(wurstFolder, "MainB.wurst")); + WFile fileMainC = WFile.create(new File(wurstFolder, "MainC.wurst")); + WFile fileWurst = WFile.create(new File(wurstFolder, "Wurst.wurst")); + + writeFile(fileMainA, string( + "package MainA", + "import DoesNotExistA", + "import DoesNotExistB", + "import DoesNotExistC", + "import DoesNotExistD", + "endpackage" + )); + writeFile(fileMainB, string( + "package MainB", + "import MissingPkg1", + "import MissingPkg2", + "import MissingPkg3", + "endpackage" + )); + writeFile(fileMainC, string( + "package MainC", + "import UnknownAlpha", + "import UnknownBeta", + "endpackage" + )); + writeFile(fileWurst, "package Wurst\n"); + + ModelManagerImpl manager = new ModelManagerImpl(projectFolder, new BufferManager()); + Map errors = keepErrorsInMap(manager); + manager.buildProject(); + + String errorsMainA = errors.getOrDefault(fileMainA, ""); + assertImportMissing(errorsMainA, "DoesNotExistA"); + assertImportMissing(errorsMainA, "DoesNotExistB"); + assertImportMissing(errorsMainA, "DoesNotExistC"); + assertImportMissing(errorsMainA, "DoesNotExistD"); + + String errorsMainB = errors.getOrDefault(fileMainB, ""); + assertImportMissing(errorsMainB, "MissingPkg1"); + assertImportMissing(errorsMainB, "MissingPkg2"); + assertImportMissing(errorsMainB, "MissingPkg3"); + + String errorsMainC = errors.getOrDefault(fileMainC, ""); + assertImportMissing(errorsMainC, "UnknownAlpha"); + assertImportMissing(errorsMainC, "UnknownBeta"); + } + + private void assertImportMissing(String diagnostics, String packageName) { + boolean hasResolvedError = diagnostics.contains("The import '" + packageName + "' could not be resolved."); + boolean hasValidatorError = diagnostics.contains("Could not find imported package " + packageName); + assertEquals(hasResolvedError || hasValidatorError, true, + "Expected missing-import diagnostic for " + packageName + " in:\n" + diagnostics); + } + @Test public void deletingFileClearsErrorsFromModel() throws IOException { File projectFolder = new File("./temp/testProject_delete_clears_errors/");