/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.tvl.goworks.editor.go.navigation; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.antlr.netbeans.editor.navigation.Description; import org.antlr.netbeans.editor.navigation.NavigatorPanelUI; import org.antlr.netbeans.editor.text.DocumentSnapshot; import org.antlr.v4.runtime.Dependents; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.RuleDependencies; import org.antlr.v4.runtime.RuleDependency; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.misc.Tuple; import org.antlr.v4.runtime.misc.Tuple3; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.works.editor.antlr4.parsing.ParseTrees; import org.tvl.goworks.editor.go.highlighter.SemanticHighlighter; import org.tvl.goworks.editor.go.navigation.GoNode.DeclarationDescription; import org.tvl.goworks.editor.go.parser.CompiledFileModel; import org.tvl.goworks.editor.go.parser.CompiledModel; import org.tvl.goworks.editor.go.parser.GoParser; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ArrayTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.BaseTypeNameContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.BlockContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.BodyContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.BuiltinCallExprContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.CallExprContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ChannelTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.CompositeLiteralContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ConstSpecContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ConversionContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ConversionOrCallExprContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ExpressionContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ExpressionListContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.FieldDeclContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.FunctionDeclContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.FunctionTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.IdentifierListContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.InterfaceTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.InterfaceTypeNameContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.MapTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.MethodDeclContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.MethodNameContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.MethodSpecContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.OperandExprContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ParameterDeclContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ParameterListContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ParametersContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.PointerTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.QualifiedIdentifierContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ReceiverContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ResultContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.ShortVarDeclContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.SignatureContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.SliceTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.SourceFileContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.StructTypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.TypeContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.TypeLiteralContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.TypeNameContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.TypeSpecContext; import org.tvl.goworks.editor.go.parser.generated.AbstractGoParser.VarSpecContext; import org.tvl.goworks.editor.go.parser.generated.GoParserBaseListener; import org.tvl.goworks.editor.go.parser.generated.GoParserBaseVisitor; /** * * @author Sam Harwell */ public class GoDeclarationsScanner { // -J-Dorg.tvl.goworks.editor.go.navigation.GoDeclarationsScanner.level=FINE private static final Logger LOGGER = Logger.getLogger(GoDeclarationsScanner.class.getName()); public Description scan(CompiledModel model) { GoDeclarationsPanel panel = GoDeclarationsPanel.getInstance(); GoDeclarationsPanelUI ui = panel != null ? panel.getComponent() : null; if (ui == null) { return null; } GoNode.DeclarationDescription rootDescription = scan(ui, model); return rootDescription; } public GoNode.DeclarationDescription scan(NavigatorPanelUI ui, CompiledModel model) { try { // don't update if there were errors and a result is already displayed /*if (!result.getParser().getSyntaxErrors().isEmpty() && !ui.isShowingWaitNode()) { return; }*/ GoNode.DeclarationDescription rootDescription = new GoNode.DeclarationDescription(); rootDescription.setFileObject(model.getSnapshot().getVersionedDocument().getFileObject()); // for (CompiledFileModel importedParseResult : model.getImportedGroupResults()) { // processParseResult(null, importedParseResult, ui, rootDescription); // } processParseResult(model.getSnapshot(), model.getResult(), ui, rootDescription); return rootDescription; } catch (RuntimeException ex) { LOGGER.log(Level.WARNING, "An exception occurred while scanning for declarations.", ex); return null; } } private void processParseResult(DocumentSnapshot snapshot, CompiledFileModel result, NavigatorPanelUI ui, GoNode.DeclarationDescription rootDescription) { SourceFileContext parseResult = result.getResult(); if (parseResult != null) { DeclarationsScannerListener listener = new DeclarationsScannerListener(snapshot, rootDescription); ParseTreeWalker.DEFAULT.walk(listener, parseResult); } } private static class DeclarationsScannerListener extends GoParserBaseListener { private final DocumentSnapshot snapshot; private final Deque<DeclarationDescription> descriptionStack = new ArrayDeque<>(); private final Deque<String> typeNameStack = new ArrayDeque<>(); private final Map<String, Description> _typeDescriptions = new HashMap<>(); /** * Name -> Parent Node -> Method Node */ private final List<Tuple3<String, ? extends Description, ? extends Description>> _methodDescriptions = new ArrayList<>(); private int resultLevel; private int blockLevel; public DeclarationsScannerListener(DocumentSnapshot snapshot, DeclarationDescription rootDescription) { this.snapshot = snapshot; this.descriptionStack.push(rootDescription); } public DeclarationDescription getCurrentParent() { return descriptionStack.peek(); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_sourceFile, version=1) public void exitSourceFile(SourceFileContext ctx) { for (Tuple3<String, ? extends Description, ? extends Description> pair : _methodDescriptions) { Description typeDescription = _typeDescriptions.get(pair.getItem1()); if (typeDescription == null) { continue; } Description parent = pair.getItem2(); parent.getChildren().remove(pair.getItem3()); typeDescription.getChildren().add(pair.getItem3()); } } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_constSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_identifierList, version=0), }) public void enterConstSpec(ConstSpecContext ctx) { if (!isTopLevel(ctx)) { return; } IdentifierListContext idListContext = ctx.identifierList(); List<? extends TerminalNode> identifiers = idListContext.IDENTIFIER(); String type = ctx.type() != null ? String.format(" : <font color='808080'>%s</font>", HtmlSignatureVisitor.UNCOLORED.visit(ctx.type())) : ""; for (TerminalNode identifier : identifiers) { Interval sourceInterval = new Interval(identifier.getSymbol().getStartIndex(), ParseTrees.getStopSymbol(ctx).getStopIndex()); String name = identifier.getSymbol().getText(); String signature = name + type; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.CONSTANT); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); } } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_varSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_identifierList, version=0), }) public void enterVarSpec(VarSpecContext ctx) { // no locals in navigator if (blockLevel > 0) { return; } IdentifierListContext idListContext = ctx.identifierList(); ExpressionListContext expressionListContext = ctx.expressionList(); List<? extends TerminalNode> identifiers = idListContext.IDENTIFIER(); List<? extends ExpressionContext> expressions = expressionListContext != null ? expressionListContext.expression() : Collections.<ExpressionContext>emptyList(); String type = ctx.type() != null ? HtmlSignatureVisitor.UNCOLORED.visit(ctx.type()) : ""; for (int i = 0; i < identifiers.size(); i++) { TerminalNode identifier = identifiers.get(i); String varType = type; if (varType.isEmpty() && expressions.size() == identifiers.size()) { varType = HtmlSignatureVisitor.UNCOLORED.visit(expressions.get(i)); } if (!varType.isEmpty()) { varType = String.format(" : <font color='808080'>%s</font>", varType); } Interval sourceInterval = new Interval(identifier.getSymbol().getStartIndex(), ParseTrees.getStopSymbol(ctx).getStopIndex()); String name = identifier.getSymbol().getText(); String signature = name + varType; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.VARIABLE); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); } } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_shortVarDecl, version=1), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_identifierList, version=0), }) public void enterShortVarDecl(ShortVarDeclContext ctx) { // no locals in navigator if (blockLevel > 0) { return; } IdentifierListContext idListContext = ctx.identifierList(); List<? extends TerminalNode> identifiers = idListContext.IDENTIFIER(); for (TerminalNode identifier : identifiers) { Interval sourceInterval = new Interval(identifier.getSymbol().getStartIndex(), ParseTrees.getStopSymbol(ctx).getStopIndex()); String name = identifier.getSymbol().getText(); String signature = name; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.VARIABLE); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(String.format("%s", Description.htmlEscape(signature))); getCurrentParent().getChildren().add(description); } } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_fieldDecl, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_structType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_identifierList, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_anonymousField, version=0), }) public void enterFieldDecl(FieldDeclContext ctx) { if (ctx.getParent() == null || isAnonymousType((StructTypeContext)ctx.getParent())) { return; } IdentifierListContext idListContext = ctx.identifierList(); if (idListContext != null) { List<? extends TerminalNode> identifiers = idListContext.IDENTIFIER(); String type = ctx.type() != null ? String.format(" : <font color='808080'>%s</font>", HtmlSignatureVisitor.UNCOLORED.visit(ctx.type())) : ""; for (TerminalNode identifier : identifiers) { Interval sourceInterval = new Interval(identifier.getSymbol().getStartIndex(), ParseTrees.getStopSymbol(ctx).getStopIndex()); String name = identifier.getSymbol().getText(); String signature = name + type; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.FIELD); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); } } else if (ctx.anonymousField() != null) { // anonymous field, add to struct node in navigator String type = HtmlSignatureVisitor.UNCOLORED.visit(ctx.anonymousField()); String headerFormat; String header = getCurrentParent().getHtmlHeader(); if (!header.contains(":")) { headerFormat = "%s : <font color='808080'>%s</font>"; } else { headerFormat = "%s, <font color='808080'>%s</font>"; } header = String.format(headerFormat, header, type); getCurrentParent().setHtmlHeader(header); } } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceType, version=0) public void enterInterfaceType(InterfaceTypeContext ctx) { if (isAnonymousType(ctx)) { return; } Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = typeNameStack.isEmpty() ? "?interface?" : typeNameStack.peek(); String signature = name; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.INTERFACE); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(String.format("%s", signature)); getCurrentParent().getChildren().add(description); descriptionStack.push(description); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceType, version=0) public void exitInterfaceType(InterfaceTypeContext ctx) { if (isAnonymousType(ctx)) { return; } descriptionStack.pop(); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_structType, version=0) public void enterStructType(StructTypeContext ctx) { if (isAnonymousType(ctx)) { return; } Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = typeNameStack.isEmpty() ? "?struct?" : typeNameStack.peek(); String signature = name; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.STRUCT); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(String.format("%s", signature)); getCurrentParent().getChildren().add(description); descriptionStack.push(description); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_structType, version=0) public void exitStructType(StructTypeContext ctx) { if (isAnonymousType(ctx)) { return; } if (isTopLevel(ctx)) { String name = descriptionStack.peek().getName(); if (!_typeDescriptions.containsKey(name)) { _typeDescriptions.put(name, descriptionStack.peek()); } } descriptionStack.pop(); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_qualifiedIdentifier, version=0) public void enterQualifiedIdentifier(QualifiedIdentifierContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_qualifiedIdentifier, version=0) public void exitQualifiedIdentifier(QualifiedIdentifierContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_arrayType, version=0) public void enterArrayType(ArrayTypeContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_arrayType, version=0) public void exitArrayType(ArrayTypeContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_pointerType, version=0) public void enterPointerType(PointerTypeContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_pointerType, version=0) public void exitPointerType(PointerTypeContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionType, version=0) public void enterFunctionType(FunctionTypeContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionType, version=0) public void exitFunctionType(FunctionTypeContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_sliceType, version=0) public void enterSliceType(SliceTypeContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_sliceType, version=0) public void exitSliceType(SliceTypeContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_mapType, version=0) public void enterMapType(MapTypeContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_mapType, version=0) public void exitMapType(MapTypeContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_channelType, version=0) public void enterChannelType(ChannelTypeContext ctx) { handleEnterTypeAlias(ctx); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_channelType, version=0) public void exitChannelType(ChannelTypeContext ctx) { handleExitTypeAlias(ctx); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceTypeName, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeName, version=0, dependents=Dependents.SELF), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodName, version=0), }) public void enterMethodSpec(MethodSpecContext ctx) { if (ctx.getParent() == null || isAnonymousType((InterfaceTypeContext)ctx.getParent())) { return; } if (ctx.interfaceTypeName() != null) { InterfaceTypeNameContext interfaceTypeNameContext = ctx.interfaceTypeName(); Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = interfaceTypeNameContext.typeName() != null ? interfaceTypeNameContext.typeName().getText() : "?"; String signature = name; GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.INTERFACE); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(String.format("%s", Description.htmlEscape(signature))); getCurrentParent().getChildren().add(description); descriptionStack.push(description); } else if (ctx.methodName() != null) { MethodNameContext methodNameContext = ctx.methodName(); Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = methodNameContext.IDENTIFIER() != null ? methodNameContext.IDENTIFIER().getText() : "?"; String signature = HtmlSignatureVisitor.COLORED.visit(ctx); GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.METHOD); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); descriptionStack.push(description); } } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceTypeName, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodName, version=0), }) public void exitMethodSpec(MethodSpecContext ctx) { if (ctx.getParent() == null || isAnonymousType((InterfaceTypeContext)ctx.getParent())) { return; } if (ctx.interfaceTypeName() != null || ctx.methodName() != null) { descriptionStack.pop(); } } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionDecl, version=0) public void enterFunctionDecl(FunctionDeclContext ctx) { Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = ctx.IDENTIFIER() != null ? ctx.IDENTIFIER().getText() : "?"; String signature = HtmlSignatureVisitor.COLORED.visit(ctx); GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.FUNCTION); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); descriptionStack.push(description); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionDecl, version=0) public void exitFunctionDecl(FunctionDeclContext ctx) { descriptionStack.pop(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodDecl, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodName, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_receiver, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_baseTypeName, version=0), }) public void enterMethodDecl(MethodDeclContext ctx) { Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = ctx.methodName() != null && ctx.methodName().IDENTIFIER() != null ? ctx.methodName().IDENTIFIER().getSymbol().getText() : "?"; String signature = HtmlSignatureVisitor.COLORED.visit(ctx); GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.METHOD); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); ReceiverContext receiverContext = ctx.receiver(); BaseTypeNameContext baseTypeNameContext = receiverContext != null ? receiverContext.baseTypeName() : null; if (baseTypeNameContext != null && baseTypeNameContext.IDENTIFIER() != null) { String receiverTypeName = baseTypeNameContext.IDENTIFIER().getText(); _methodDescriptions.add(Tuple.create(receiverTypeName, getCurrentParent(), description)); } descriptionStack.push(description); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodDecl, version=0) public void exitMethodDecl(MethodDeclContext ctx) { descriptionStack.pop(); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeSpec, version=0) public void enterTypeSpec(TypeSpecContext ctx) { if (ctx.IDENTIFIER() != null) { typeNameStack.push(ctx.IDENTIFIER().getSymbol().getText()); } else { typeNameStack.push("?"); } } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeSpec, version=0) public void exitTypeSpec(TypeSpecContext ctx) { typeNameStack.pop(); } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_result, version=0) public void enterResult(ResultContext ctx) { resultLevel++; } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_result, version=0) public void exitResult(ResultContext ctx) { resultLevel--; } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_block, version=0) public void enterBlock(BlockContext ctx) { blockLevel++; } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_block, version=0) public void exitBlock(BlockContext ctx) { blockLevel--; } @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_structType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeLiteral, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_type, version=2, dependents=Dependents.PARENTS), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeSpec, version=0), }) private boolean isAnonymousType(ParserRuleContext context) { if (!(context instanceof InterfaceTypeContext) && !(context instanceof StructTypeContext)) { throw new IllegalArgumentException(); } // this handles the case of null parent and literalType for a compositeLiteral if (!(context.getParent() instanceof TypeLiteralContext)) { return true; } TypeLiteralContext typeLiteralContext = (TypeLiteralContext)context.getParent(); if (typeLiteralContext.getParent() == null) { return true; } TypeContext typeContext = (TypeContext)typeLiteralContext.getParent(); if (!(typeContext.getParent() instanceof TypeSpecContext)) { return true; } return false; } @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_constSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_body, version=0), }) private boolean isTopLevel(ConstSpecContext context) { if (ParseTrees.findAncestor(context, BodyContext.class) != null) { return false; } return true; } @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_structType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_body, version=0), }) private boolean isTopLevel(StructTypeContext context) { if (isAnonymousType(context)) { return false; } if (ParseTrees.findAncestor(context, BodyContext.class) != null) { return false; } return true; } @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_qualifiedIdentifier, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_arrayType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_pointerType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_sliceType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_mapType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_channelType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeLiteral, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_type, version=2, dependents=Dependents.PARENTS), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_body, version=0), }) private boolean isTypeAlias(ParserRuleContext context, boolean topLevelOnly) { switch (context.getRuleIndex()) { case GoParser.RULE_qualifiedIdentifier: case GoParser.RULE_arrayType: case GoParser.RULE_pointerType: case GoParser.RULE_functionType: case GoParser.RULE_sliceType: case GoParser.RULE_mapType: case GoParser.RULE_channelType: break; default: throw new IllegalArgumentException(); } TypeSpecContext typeSpecContext = null; if (context.getParent() instanceof TypeLiteralContext) { TypeLiteralContext typeLiteralContext = (TypeLiteralContext)context.getParent(); if (!(typeLiteralContext.getParent() instanceof TypeContext)) { return false; } if (!(typeLiteralContext.getParent().getParent() instanceof TypeSpecContext)) { return false; } typeSpecContext = (TypeSpecContext)typeLiteralContext.getParent().getParent(); } else if (context.getParent() instanceof TypeNameContext) { TypeNameContext typeNameContext = (TypeNameContext)context.getParent(); if (!(typeNameContext.getParent() instanceof TypeContext)) { return false; } if (!(typeNameContext.getParent().getParent() instanceof TypeSpecContext)) { return false; } typeSpecContext = (TypeSpecContext)typeNameContext.getParent().getParent(); } if (typeSpecContext == null) { return false; } if (!topLevelOnly) { return true; } return ParseTrees.findAncestor(typeSpecContext, BodyContext.class) == null; } private boolean handleEnterTypeAlias(ParserRuleContext ctx) { if (!isTypeAlias(ctx, false)) { return false; } Interval sourceInterval = ParseTrees.getSourceInterval(ctx); String name = typeNameStack.isEmpty() ? "?" : typeNameStack.peek(); String signature = String.format("%s : <font color='808080'>%s</font>", Description.htmlEscape(name), HtmlSignatureVisitor.UNCOLORED.visit(ctx.getParent())); GoNode.DeclarationDescription description = new GoNode.DeclarationDescription(name, DeclarationKind.TYPEDEF); description.setOffset(snapshot, getCurrentParent().getFileObject(), sourceInterval.a); description.setHtmlHeader(signature); getCurrentParent().getChildren().add(description); descriptionStack.push(description); return true; } private boolean handleExitTypeAlias(ParserRuleContext context) { if (!isTypeAlias(context, false)) { return false; } if (isTypeAlias(context, true)) { String name = descriptionStack.peek().getName(); if (!_typeDescriptions.containsKey(name)) { _typeDescriptions.put(name, descriptionStack.peek()); } } descriptionStack.pop(); return true; } } public static class HtmlSignatureVisitor extends GoParserBaseVisitor<String> { public static final HtmlSignatureVisitor COLORED = new HtmlSignatureVisitor(true); public static final HtmlSignatureVisitor UNCOLORED = new HtmlSignatureVisitor(false); private final boolean _colored; public HtmlSignatureVisitor(boolean colored) { this._colored = colored; } @Override protected String defaultResult() { return ""; } @Override protected String aggregateResult(String aggregate, String nextResult) { if (aggregate == null || aggregate.isEmpty()) { return nextResult; } else if (nextResult == null || nextResult.isEmpty()) { return aggregate; } return aggregate + ", " + nextResult; } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_expression, version=1, dependents=Dependents.PARENTS) public String visitBuiltinCallExpr(BuiltinCallExprContext ctx) { return ""; } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_expression, version=1, dependents=Dependents.PARENTS) public String visitConversionOrCallExpr(ConversionOrCallExprContext ctx) { return super.visitConversionOrCallExpr(ctx); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_conversion, version=0, dependents=Dependents.PARENTS), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_type, version=0, dependents=Dependents.SELF), }) public String visitConversion(ConversionContext ctx) { if (ctx.type() != null) { String result = visit(ctx.type()); if (!result.matches("\\w+(?:\\.\\w+)?") || SemanticHighlighter.PREDEFINED_TYPES.contains(result)) { return result; } LOGGER.log(Level.FINE, "Cannot distinguish conversion from call for type or method ''{0}''", result); } return ""; } @Override @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_expression, version=1, dependents=Dependents.PARENTS) public String visitCallExpr(CallExprContext ctx) { return ""; } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionDecl, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_signature, version=0), }) public String visitFunctionDecl(FunctionDeclContext ctx) { // name(args) return StringBuilder result = new StringBuilder(); if (ctx.IDENTIFIER() != null) { result.append(Description.htmlEscape(ctx.IDENTIFIER().getText())); } if (ctx.signature() != null) { result.append(visit(ctx.signature())); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodDecl, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodName, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_signature, version=0), }) public String visitMethodDecl(MethodDeclContext ctx) { // name(args) return StringBuilder result = new StringBuilder(); if (ctx.methodName() != null) { result.append(Description.htmlEscape(ctx.methodName().getText())); } if (ctx.signature() != null) { result.append(visit(ctx.signature())); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodSpec, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceTypeName, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodName, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_signature, version=0), }) public String visitMethodSpec(MethodSpecContext ctx) { if (ctx.interfaceTypeName() != null) { return Description.htmlEscape(ctx.interfaceTypeName().getText()); } StringBuilder result = new StringBuilder(); if (ctx.methodName() != null) { result.append(Description.htmlEscape(ctx.methodName().getText())); } if (ctx.signature() != null) { result.append(visit(ctx.signature())); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_signature, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_parameters, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_result, version=0), }) public String visitSignature(SignatureContext ctx) { StringBuilder result = new StringBuilder(); if (ctx.parameters() != null) { result.append(visit(ctx.parameters())); } result.append(' '); if (ctx.result() != null) { result.append(visit(ctx.result())); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_parameters, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_parameterList, version=0), }) public String visitParameters(ParametersContext ctx) { StringBuilder result = new StringBuilder(); result.append('('); if (ctx.parameterList() != null) { result.append(visit(ctx.parameterList())); } result.append(')'); return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_parameterList, version=0), }) public String visitParameterList(ParameterListContext ctx) { // default impl does the right thing return super.visitParameterList(ctx); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_parameterDecl, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_identifierList, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_type, version=0, dependents=Dependents.SELF), }) public String visitParameterDecl(ParameterDeclContext ctx) { StringBuilder result = new StringBuilder(); if (ctx.identifierList() != null) { result.append(visit(ctx.identifierList())); result.append(' '); } if (_colored) { result.append("<font color='808080'>"); } if (ctx.ellip != null) { result.append("..."); } if (ctx.type() != null) { result.append(UNCOLORED.visit(ctx.type())); } if (_colored) { result.append("</font>"); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_identifierList, version=0), }) public String visitIdentifierList(IdentifierListContext ctx) { StringBuilder result = new StringBuilder(); for (TerminalNode node : ctx.IDENTIFIER()) { if (result.length() > 0) { result.append(", "); } result.append(Description.htmlEscape(node.getText())); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_type, version=2, dependents=Dependents.PARENTS), }) public String visitType(TypeContext ctx) { // default impl does the right thing return super.visitType(ctx); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeName, version=3, dependents=Dependents.PARENTS), }) public String visitTypeName(TypeNameContext ctx) { return Description.htmlEscape(ctx.getText()); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_typeLiteral, version=0), }) public String visitTypeLiteral(TypeLiteralContext ctx) { // default impl does the right thing return super.visitTypeLiteral(ctx); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_expression, version=1), }) public String visitOperandExpr(OperandExprContext ctx) { // default impl does the right thing return super.visitOperandExpr(ctx); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_compositeLiteral, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_literalType, version=0), }) public String visitCompositeLiteral(CompositeLiteralContext ctx) { if (ctx.literalType() != null) { return visit(ctx.literalType()); } return ""; } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_arrayType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_elementType, version=0), }) public String visitArrayType(ArrayTypeContext ctx) { if (ctx.elementType() == null) { return "[...]?"; } return "[...]" + visit(ctx.elementType()); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_structType, version=0), }) public String visitStructType(StructTypeContext ctx) { if (ctx.fieldDecl().isEmpty()) { return "struct{}"; } return "struct{...}"; } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_pointerType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_baseType, version=0), }) public String visitPointerType(PointerTypeContext ctx) { if (ctx.baseType() == null) { return "*?"; } return "*" + visit(ctx.baseType()); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_functionType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_signature, version=0), }) public String visitFunctionType(FunctionTypeContext ctx) { if (ctx.signature() != null) { return "func" + UNCOLORED.visit(ctx.signature()); } else { return "func?"; } } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_interfaceType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_methodSpec, version=0), }) public String visitInterfaceType(InterfaceTypeContext ctx) { if (ctx.methodSpec().isEmpty()) { return "interface{}"; } return "interface{?}"; } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_sliceType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_elementType, version=0), }) public String visitSliceType(SliceTypeContext ctx) { if (ctx.elementType() == null) { return "[]?"; } return "[]" + visit(ctx.elementType()); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_mapType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_keyType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_elementType, version=0), }) public String visitMapType(MapTypeContext ctx) { String keyType = ctx.keyType() != null ? visit(ctx.keyType()) : "?"; String elementType = ctx.elementType() != null ? visit(ctx.elementType()) : "?"; return String.format("map[%s]%s", keyType, elementType); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_channelType, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_elementType, version=0), }) public String visitChannelType(ChannelTypeContext ctx) { StringBuilder result = new StringBuilder(); if (ctx.recv != null) { result.append("<-"); } result.append("chan"); if (ctx.send != null) { result.append("<-"); } result.append(' '); if (ctx.elementType() != null) { result.append(visit(ctx.elementType())); } return result.toString(); } @Override @RuleDependencies({ @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_result, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_parameters, version=0), @RuleDependency(recognizer=GoParser.class, rule=GoParser.RULE_type, version=0, dependents=Dependents.SELF), }) public String visitResult(ResultContext ctx) { if (ctx.parameters() != null) { return visit(ctx.parameters()); } else if (ctx.type() != null) { if (_colored) { return String.format("<font color='808080'>%s</font>", UNCOLORED.visit(ctx.type())); } else { return visit(ctx.type()); } } else { return ""; } } } }