Java tutorial
/* * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.ballerinalang.langserver.completions; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.TokenStream; import org.ballerinalang.langserver.common.LSNodeVisitor; import org.ballerinalang.langserver.common.UtilSymbolKeys; import org.ballerinalang.langserver.common.utils.CommonUtil; import org.ballerinalang.langserver.compiler.DocumentServiceKeys; import org.ballerinalang.langserver.compiler.LSContext; import org.ballerinalang.langserver.completions.util.CursorPositionResolvers; import org.ballerinalang.langserver.completions.util.positioning.resolvers.BlockStatementScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.CursorPositionResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.FunctionNodeScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.InvocationParameterScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.MatchExpressionScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.MatchStatementScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.ObjectTypeScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.RecordScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.ResourceParamScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.ServiceScopeResolver; import org.ballerinalang.langserver.completions.util.positioning.resolvers.TopLevelNodeScopeResolver; import org.ballerinalang.model.tree.Node; import org.ballerinalang.model.tree.TopLevelNode; import org.ballerinalang.model.tree.statements.StatementNode; import org.eclipse.lsp4j.Position; import org.wso2.ballerinalang.compiler.parser.antlr4.BallerinaParser; import org.wso2.ballerinalang.compiler.semantics.analyzer.SymbolResolver; import org.wso2.ballerinalang.compiler.semantics.model.Scope; import org.wso2.ballerinalang.compiler.semantics.model.SymbolEnv; import org.wso2.ballerinalang.compiler.semantics.model.SymbolTable; import org.wso2.ballerinalang.compiler.semantics.model.symbols.BSymbol; import org.wso2.ballerinalang.compiler.tree.BLangAnnotationAttachment; import org.wso2.ballerinalang.compiler.tree.BLangEndpoint; import org.wso2.ballerinalang.compiler.tree.BLangEnum; import org.wso2.ballerinalang.compiler.tree.BLangFunction; import org.wso2.ballerinalang.compiler.tree.BLangImportPackage; import org.wso2.ballerinalang.compiler.tree.BLangNode; import org.wso2.ballerinalang.compiler.tree.BLangPackage; import org.wso2.ballerinalang.compiler.tree.BLangResource; import org.wso2.ballerinalang.compiler.tree.BLangService; import org.wso2.ballerinalang.compiler.tree.BLangTypeDefinition; import org.wso2.ballerinalang.compiler.tree.BLangVariable; import org.wso2.ballerinalang.compiler.tree.BLangWorker; import org.wso2.ballerinalang.compiler.tree.BLangXMLNS; import org.wso2.ballerinalang.compiler.tree.expressions.BLangBinaryExpr; import org.wso2.ballerinalang.compiler.tree.expressions.BLangBracedOrTupleExpr; import org.wso2.ballerinalang.compiler.tree.expressions.BLangInvocation; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef; import org.wso2.ballerinalang.compiler.tree.expressions.BLangTypeConversionExpr; import org.wso2.ballerinalang.compiler.tree.statements.BLangAbort; import org.wso2.ballerinalang.compiler.tree.statements.BLangAssignment; import org.wso2.ballerinalang.compiler.tree.statements.BLangBind; import org.wso2.ballerinalang.compiler.tree.statements.BLangBlockStmt; import org.wso2.ballerinalang.compiler.tree.statements.BLangBreak; import org.wso2.ballerinalang.compiler.tree.statements.BLangCatch; import org.wso2.ballerinalang.compiler.tree.statements.BLangCompensate; import org.wso2.ballerinalang.compiler.tree.statements.BLangContinue; import org.wso2.ballerinalang.compiler.tree.statements.BLangExpressionStmt; import org.wso2.ballerinalang.compiler.tree.statements.BLangForeach; import org.wso2.ballerinalang.compiler.tree.statements.BLangForkJoin; import org.wso2.ballerinalang.compiler.tree.statements.BLangIf; import org.wso2.ballerinalang.compiler.tree.statements.BLangLock; import org.wso2.ballerinalang.compiler.tree.statements.BLangMatch; import org.wso2.ballerinalang.compiler.tree.statements.BLangReturn; import org.wso2.ballerinalang.compiler.tree.statements.BLangScope; import org.wso2.ballerinalang.compiler.tree.statements.BLangThrow; import org.wso2.ballerinalang.compiler.tree.statements.BLangTransaction; import org.wso2.ballerinalang.compiler.tree.statements.BLangTryCatchFinally; import org.wso2.ballerinalang.compiler.tree.statements.BLangVariableDef; import org.wso2.ballerinalang.compiler.tree.statements.BLangWhile; import org.wso2.ballerinalang.compiler.tree.statements.BLangWorkerReceive; import org.wso2.ballerinalang.compiler.tree.statements.BLangWorkerSend; import org.wso2.ballerinalang.compiler.tree.types.BLangObjectTypeNode; import org.wso2.ballerinalang.compiler.tree.types.BLangRecordTypeNode; import org.wso2.ballerinalang.compiler.util.CompilerContext; import org.wso2.ballerinalang.compiler.util.Name; import org.wso2.ballerinalang.compiler.util.Names; import org.wso2.ballerinalang.compiler.util.diagnotic.DiagnosticPos; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Stack; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.Nonnull; /** * @since 0.94 */ public class TreeVisitor extends LSNodeVisitor { private boolean terminateVisitor = false; private int loopCount = 0; private int transactionCount = 0; private SymbolEnv symbolEnv; private SymbolResolver symbolResolver; private SymbolTable symTable; private Stack<Node> blockOwnerStack; private Stack<BLangBlockStmt> blockStmtStack; private Stack<Boolean> isCurrentNodeTransactionStack; private Class cursorPositionResolver; private LSContext lsContext; private BLangNode previousNode = null; public TreeVisitor(LSContext documentServiceContext) { this.lsContext = documentServiceContext; init(this.lsContext.get(DocumentServiceKeys.COMPILER_CONTEXT_KEY)); } private void init(CompilerContext compilerContext) { blockOwnerStack = new Stack<>(); blockStmtStack = new Stack<>(); isCurrentNodeTransactionStack = new Stack<>(); symTable = SymbolTable.getInstance(compilerContext); symbolResolver = SymbolResolver.getInstance(compilerContext); lsContext.put(DocumentServiceKeys.SYMBOL_TABLE_KEY, symTable); } /////////////////////////////////// ///// Visitor Methods ///// /////////////////////////////////// @Override public void visit(BLangPackage pkgNode) { SymbolEnv pkgEnv = this.symTable.pkgEnvMap.get(pkgNode.symbol); this.symbolEnv = pkgEnv; List<TopLevelNode> topLevelNodes = pkgNode.topLevelNodes.stream() .filter(filterNodeByCompilationUnit(lsContext.get(DocumentServiceKeys.FILE_NAME_KEY))) .collect(Collectors.toList()); List<BLangImportPackage> imports = pkgNode.getImports().stream() .filter(filterNodeByCompilationUnit(lsContext.get(DocumentServiceKeys.FILE_NAME_KEY))) .collect(Collectors.toList()); imports.forEach(bLangImportPackage -> { cursorPositionResolver = TopLevelNodeScopeResolver.class; this.blockOwnerStack.push(pkgNode); acceptNode(bLangImportPackage, pkgEnv); }); topLevelNodes.forEach(topLevelNode -> { cursorPositionResolver = TopLevelNodeScopeResolver.class; this.blockOwnerStack.push(pkgNode); acceptNode((BLangNode) topLevelNode, pkgEnv); }); // If the cursor is at an empty document's first line or is bellow the last construct, symbol env node is null if (this.lsContext.get(CompletionKeys.SYMBOL_ENV_NODE_KEY) == null) { this.lsContext.put(CompletionKeys.SYMBOL_ENV_NODE_KEY, pkgNode); this.populateSymbols(this.resolveAllVisibleSymbols(this.getSymbolEnv()), this.getSymbolEnv()); forceTerminateVisitor(); } } @Override public void visit(BLangImportPackage importPkgNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(importPkgNode.getPosition(), importPkgNode, this, lsContext); } @Override public void visit(BLangXMLNS xmlnsNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(xmlnsNode.getPosition(), xmlnsNode, this, this.lsContext); } @Override public void visit(BLangFunction funcNode) { String functionName = funcNode.getName().getValue(); SymbolEnv funcEnv = SymbolEnv.createFunctionEnv(funcNode, funcNode.symbol.scope, this.symbolEnv); CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(this.cursorPositionResolver); funcNode.annAttachments.forEach(annotationAttachment -> this.acceptNode(annotationAttachment, funcEnv)); funcNode.docAttachments.forEach(bLangDocumentation -> this.acceptNode(bLangDocumentation, funcEnv)); if (terminateVisitor || cpr.isCursorBeforeNode(funcNode.getPosition(), funcNode, this, this.lsContext) || isWithinParameterContext(functionName, UtilSymbolKeys.FUNCTION_KEYWORD_KEY, funcEnv)) { return; } funcNode.endpoints.forEach(bLangEndpoint -> this.acceptNode(bLangEndpoint, funcEnv)); if (!funcNode.getWorkers().isEmpty()) { funcNode.workers.forEach(e -> { this.blockOwnerStack.push(funcNode); this.cursorPositionResolver = FunctionNodeScopeResolver.class; this.acceptNode(e, funcEnv); this.blockOwnerStack.pop(); }); } else if (funcNode.getBody() != null) { this.blockOwnerStack.push(funcNode); this.cursorPositionResolver = BlockStatementScopeResolver.class; this.acceptNode(funcNode.body, funcEnv); this.blockOwnerStack.pop(); } } @Override public void visit(BLangTypeDefinition typeDefinition) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(typeDefinition.getPosition(), typeDefinition, this, this.lsContext)) { return; } this.acceptNode(typeDefinition.typeNode, symbolEnv); } @Override public void visit(BLangRecordTypeNode recordTypeNode) { BSymbol recordSymbol = recordTypeNode.symbol; CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); SymbolEnv recordEnv = SymbolEnv.createPkgLevelSymbolEnv(recordTypeNode, recordSymbol.scope, symbolEnv); // TODO: Since the position of the record type node is invalid, we pass the position of the type definition if (recordSymbol.getName().getValue().contains(UtilSymbolKeys.DOLLAR_SYMBOL_KEY) || cpr.isCursorBeforeNode(recordTypeNode.parent.getPosition(), recordTypeNode, this, this.lsContext) || (recordTypeNode.fields.isEmpty() && isCursorWithinBlock(recordTypeNode.parent.getPosition(), recordEnv))) { return; } cursorPositionResolver = RecordScopeResolver.class; this.blockOwnerStack.push(recordTypeNode); recordTypeNode.fields.forEach(field -> acceptNode(field, recordEnv)); cursorPositionResolver = TopLevelNodeScopeResolver.class; this.blockOwnerStack.pop(); } @Override public void visit(BLangObjectTypeNode objectTypeNode) { BSymbol objectSymbol = objectTypeNode.symbol; SymbolEnv objectEnv = SymbolEnv.createPkgLevelSymbolEnv(objectTypeNode, objectSymbol.scope, symbolEnv); // TODO: Currently consider the type definition's position since the object body's position is wrong if (objectTypeNode.fields.isEmpty() && objectTypeNode.functions.isEmpty() && this.isCursorWithinBlock(objectTypeNode.parent.getPosition(), objectEnv)) { return; } blockOwnerStack.push(objectTypeNode); objectTypeNode.fields.forEach(field -> { this.cursorPositionResolver = ObjectTypeScopeResolver.class; acceptNode(field, objectEnv); }); // TODO: visit annotation and doc attachments objectTypeNode.functions.forEach(f -> { this.cursorPositionResolver = ObjectTypeScopeResolver.class; acceptNode(f, objectEnv); }); blockOwnerStack.pop(); this.cursorPositionResolver = TopLevelNodeScopeResolver.class; } @Override public void visit(BLangVariable varNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(varNode.getPosition(), varNode, this, this.lsContext) || varNode.expr == null) { return; } // This is an endpoint definition this.acceptNode(varNode.expr, symbolEnv); } @Override public void visit(BLangBinaryExpr binaryExpr) { binaryExpr.getLeftExpression().accept(this); binaryExpr.getRightExpression().accept(this); } @Override public void visit(BLangBracedOrTupleExpr bracedOrTupleExpr) { bracedOrTupleExpr.getExpressions().forEach(bLangExpression -> this.acceptNode(bLangExpression, symbolEnv)); } @Override public void visit(BLangTypeConversionExpr conversionExpr) { conversionExpr.expr.accept(this); } @Override public void visit(BLangBlockStmt blockNode) { SymbolEnv blockEnv = SymbolEnv.createBlockEnv(blockNode, symbolEnv); // Reset the previous node to null this.setPreviousNode(null); if (blockNode.stmts.isEmpty() && this .isCursorWithinBlock((DiagnosticPos) (this.blockOwnerStack.peek()).getPosition(), blockEnv)) { return; } this.blockStmtStack.push(blockNode); this.cursorPositionResolver = BlockStatementScopeResolver.class; blockNode.stmts.forEach(stmt -> this.acceptNode(stmt, blockEnv)); this.blockStmtStack.pop(); } @Override public void visit(BLangVariableDef varDefNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(varDefNode.getPosition(), varDefNode, this, this.lsContext)) { return; } this.acceptNode(varDefNode.var, symbolEnv); } @Override public void visit(BLangAssignment assignNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(assignNode.getPosition(), assignNode, this, this.lsContext)) { return; } this.acceptNode(assignNode.expr, symbolEnv); } @Override public void visit(BLangExpressionStmt exprStmtNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(exprStmtNode.getPosition(), exprStmtNode, this, this.lsContext) || !(exprStmtNode.expr instanceof BLangInvocation)) { return; } this.acceptNode(exprStmtNode.expr, symbolEnv); } @Override public void visit(BLangInvocation invocationNode) { int curLine = lsContext.get(DocumentServiceKeys.POSITION_KEY).getPosition().getLine(); CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(invocationNode.getPosition(), invocationNode, this, this.lsContext) || curLine != invocationNode.getPosition().getStartLine() - 1) { return; } final TreeVisitor visitor = this; Class fallbackCursorPositionResolver = this.cursorPositionResolver; this.cursorPositionResolver = InvocationParameterScopeResolver.class; this.blockOwnerStack.push(invocationNode); // Visit all arguments invocationNode.getArgumentExpressions().forEach(expressionNode -> { BLangNode node = ((BLangNode) expressionNode); CursorPositionResolver posResolver = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); posResolver.isCursorBeforeNode(node.getPosition(), node, visitor, visitor.lsContext); visitor.acceptNode(node, symbolEnv); }); this.blockOwnerStack.pop(); this.cursorPositionResolver = fallbackCursorPositionResolver; } @Override public void visit(BLangIf ifNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(ifNode.getPosition(), ifNode, this, this.lsContext)) { return; } this.blockOwnerStack.push(ifNode); this.acceptNode(ifNode.body, symbolEnv); this.blockOwnerStack.pop(); if (ifNode.elseStmt != null) { acceptNode(ifNode.elseStmt, symbolEnv); } } @Override public void visit(BLangWhile whileNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(whileNode.getPosition(), whileNode, this, this.lsContext)) { return; } this.blockOwnerStack.push(whileNode); loopCount++; this.acceptNode(whileNode.body, symbolEnv); loopCount--; this.blockOwnerStack.pop(); } @Override public void visit(BLangService serviceNode) { BSymbol serviceSymbol = serviceNode.symbol; SymbolEnv serviceEnv = SymbolEnv.createPkgLevelSymbolEnv(serviceNode, serviceSymbol.scope, symbolEnv); CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); serviceNode.annAttachments .forEach(annotationAttachment -> this.acceptNode(annotationAttachment, serviceEnv)); // Reset the previous node this.setPreviousNode(null); if (cpr.isCursorBeforeNode(serviceNode.getPosition(), serviceNode, this, this.lsContext) || (serviceNode.resources.isEmpty() && serviceNode.vars.isEmpty() && serviceNode.endpoints.isEmpty() && this.isCursorWithinBlock(serviceNode.getPosition(), serviceEnv))) { return; } this.blockOwnerStack.push(serviceNode); serviceNode.endpoints.forEach(bLangEndpoint -> this.acceptNode(bLangEndpoint, serviceEnv)); serviceNode.vars.forEach(v -> { this.cursorPositionResolver = ServiceScopeResolver.class; this.acceptNode(v, serviceEnv); }); serviceNode.resources.forEach(r -> { this.cursorPositionResolver = ServiceScopeResolver.class; this.acceptNode(r, serviceEnv); }); this.blockOwnerStack.pop(); } @Override public void visit(BLangResource resourceNode) { BSymbol resourceSymbol = resourceNode.symbol; String resourceName = resourceNode.getName().getValue(); CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); SymbolEnv resourceEnv = SymbolEnv.createResourceActionSymbolEnv(resourceNode, resourceSymbol.scope, symbolEnv); resourceNode.annAttachments .forEach(annotationAttachment -> this.acceptNode(annotationAttachment, resourceEnv)); if (terminateVisitor || this.isCursorAtResourceIdentifier(resourceNode, lsContext) || isWithinParameterContext(resourceName, UtilSymbolKeys.RESOURCE_KEYWORD_KEY, resourceEnv) || cpr.isCursorBeforeNode(resourceNode.getPosition(), resourceNode, this, this.lsContext)) { return; } resourceNode.endpoints.forEach(bLangEndpoint -> this.acceptNode(bLangEndpoint, resourceEnv)); cursorPositionResolver = ResourceParamScopeResolver.class; resourceNode.workers.forEach(w -> { this.blockOwnerStack.push(resourceNode); this.cursorPositionResolver = FunctionNodeScopeResolver.class; this.acceptNode(w, resourceEnv); this.blockOwnerStack.pop(); }); this.blockOwnerStack.push(resourceNode); cursorPositionResolver = BlockStatementScopeResolver.class; acceptNode(resourceNode.body, resourceEnv); this.blockOwnerStack.pop(); } @Override public void visit(BLangTryCatchFinally tryCatchFinally) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(tryCatchFinally.getPosition(), tryCatchFinally, this, this.lsContext)) { return; } this.blockOwnerStack.push(tryCatchFinally); this.acceptNode(tryCatchFinally.tryBody, symbolEnv); this.blockOwnerStack.pop(); tryCatchFinally.catchBlocks.forEach(c -> { this.blockOwnerStack.push(c); this.acceptNode(c, symbolEnv); this.blockOwnerStack.pop(); }); if (tryCatchFinally.finallyBody != null) { this.blockOwnerStack.push(tryCatchFinally); this.acceptNode(tryCatchFinally.finallyBody, symbolEnv); this.blockOwnerStack.pop(); } } @Override public void visit(BLangCatch bLangCatch) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(bLangCatch.getPosition(), bLangCatch, this, this.lsContext)) { return; } SymbolEnv catchBlockEnv = SymbolEnv.createBlockEnv(bLangCatch.body, symbolEnv); this.acceptNode(bLangCatch.param, catchBlockEnv); this.blockOwnerStack.push(bLangCatch); this.acceptNode(bLangCatch.body, catchBlockEnv); this.blockOwnerStack.pop(); } @Override public void visit(BLangTransaction transactionNode) { this.blockOwnerStack.push(transactionNode); this.isCurrentNodeTransactionStack.push(true); this.transactionCount++; this.acceptNode(transactionNode.transactionBody, symbolEnv); this.blockOwnerStack.pop(); this.isCurrentNodeTransactionStack.pop(); this.transactionCount--; if (transactionNode.onRetryBody != null) { this.blockOwnerStack.push(transactionNode); this.acceptNode(transactionNode.onRetryBody, symbolEnv); this.blockOwnerStack.pop(); } } @Override public void visit(BLangAbort abortNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(abortNode.getPosition(), abortNode, this, this.lsContext); } @Override public void visit(BLangForkJoin forkJoin) { SymbolEnv folkJoinEnv = SymbolEnv.createFolkJoinEnv(forkJoin, this.symbolEnv); forkJoin.workers.forEach(e -> this.acceptNode(e, folkJoinEnv)); /* create code black and environment for join result section, i.e. (map results) */ BLangBlockStmt joinResultsBlock = this.generateCodeBlock(this.createVarDef(forkJoin.joinResultVar)); SymbolEnv joinResultsEnv = SymbolEnv.createBlockEnv(joinResultsBlock, this.symbolEnv); this.acceptNode(joinResultsBlock, joinResultsEnv); /* create an environment for the join body, making the enclosing environment the earlier * join result's environment */ SymbolEnv joinBodyEnv = SymbolEnv.createBlockEnv(forkJoin.joinedBody, joinResultsEnv); this.acceptNode(forkJoin.joinedBody, joinBodyEnv); if (forkJoin.timeoutExpression != null) { /* create code black and environment for timeout section */ BLangBlockStmt timeoutVarBlock = this.generateCodeBlock(this.createVarDef(forkJoin.timeoutVariable)); SymbolEnv timeoutVarEnv = SymbolEnv.createBlockEnv(timeoutVarBlock, this.symbolEnv); this.acceptNode(timeoutVarBlock, timeoutVarEnv); /* create an environment for the timeout body, making the enclosing environment the earlier * timeout var's environment */ SymbolEnv timeoutBodyEnv = SymbolEnv.createBlockEnv(forkJoin.timeoutBody, timeoutVarEnv); this.acceptNode(forkJoin.timeoutBody, timeoutBodyEnv); } } @Override public void visit(BLangWorker workerNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(workerNode.getPosition(), workerNode, this, this.lsContext)) { return; } SymbolEnv workerEnv = SymbolEnv.createWorkerEnv(workerNode, this.symbolEnv); this.blockOwnerStack.push(workerNode); this.acceptNode(workerNode.body, workerEnv); this.blockOwnerStack.pop(); } @Override public void visit(BLangWorkerSend workerSendNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(workerSendNode.getPosition(), workerSendNode, this, this.lsContext); } @Override public void visit(BLangWorkerReceive workerReceiveNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(workerReceiveNode.getPosition(), workerReceiveNode, this, this.lsContext); } @Override public void visit(BLangReturn returnNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(returnNode.getPosition(), returnNode, this, this.lsContext)) { return; } this.acceptNode(returnNode.expr, symbolEnv); } @Override public void visit(BLangContinue continueNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(continueNode.getPosition(), continueNode, this, this.lsContext); } @Override public void visit(BLangEnum enumNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(enumNode.getPosition(), enumNode, this, this.lsContext); } @Override public void visit(BLangBind bindNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(bindNode.getPosition(), bindNode, this, this.lsContext); } @Override public void visit(BLangBreak breakNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(breakNode.getPosition(), breakNode, this, this.lsContext); } @Override public void visit(BLangThrow throwNode) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(throwNode.getPosition(), throwNode, this, this.lsContext); } @Override public void visit(BLangLock lockNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(lockNode.getPosition(), lockNode, this, this.lsContext)) { return; } this.blockOwnerStack.push(lockNode); this.acceptNode(lockNode.body, symbolEnv); this.blockOwnerStack.pop(); } @Override public void visit(BLangForeach foreach) { if (!CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(foreach.getPosition(), foreach, this, this.lsContext)) { this.blockOwnerStack.push(foreach); loopCount++; this.acceptNode(foreach.body, symbolEnv); loopCount--; this.blockOwnerStack.pop(); } } @Override public void visit(BLangEndpoint endpointNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); SymbolEnv epEnv = SymbolEnv.createPkgLevelSymbolEnv(endpointNode, symbolEnv.scope, symbolEnv); endpointNode.annAttachments.forEach(annotationAttachment -> this.acceptNode(annotationAttachment, epEnv)); if (cpr.isCursorBeforeNode(endpointNode.getPosition(), endpointNode, this, this.lsContext)) { return; } this.isCursorWithinBlock(endpointNode.getPosition(), epEnv); } @Override public void visit(BLangMatch matchNode) { if (!CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(matchNode.getPosition(), matchNode, this, this.lsContext)) { this.blockOwnerStack.push(matchNode); matchNode.getPatternClauses().forEach(patternClause -> { cursorPositionResolver = MatchStatementScopeResolver.class; acceptNode(patternClause, symbolEnv); }); this.blockOwnerStack.pop(); } } @Override public void visit(BLangMatch.BLangMatchStmtPatternClause patternClause) { if (!CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(patternClause.getPosition(), patternClause, this, this.lsContext)) { blockOwnerStack.push(patternClause); // If the variable is not equal to '_', then define the variable in the block scope if (!patternClause.variable.name.value.endsWith(Names.IGNORE.value)) { SymbolEnv blockEnv = SymbolEnv.createBlockEnv(patternClause.body, symbolEnv); cursorPositionResolver = BlockStatementScopeResolver.class; acceptNode(patternClause.body, blockEnv); blockOwnerStack.pop(); return; } // TODO: Check with the semantic analyzer implementation as well. acceptNode(patternClause.body, symbolEnv); blockOwnerStack.pop(); } } @Override public void visit(BLangAnnotationAttachment annAttachmentNode) { SymbolEnv annotationAttachmentEnv = new SymbolEnv(annAttachmentNode, symbolEnv.scope); this.isCursorWithinBlock(annAttachmentNode.getPosition(), annotationAttachmentEnv); } @Override public void visit(BLangMatchExpression bLangMatchExpression) { if (!CursorPositionResolvers.getResolverByClass(cursorPositionResolver).isCursorBeforeNode( bLangMatchExpression.getPosition(), bLangMatchExpression, this, this.lsContext)) { SymbolEnv matchExprEnv = new SymbolEnv(bLangMatchExpression, symbolEnv.scope); final TreeVisitor visitor = this; Class fallbackCursorPositionResolver = this.cursorPositionResolver; this.cursorPositionResolver = MatchExpressionScopeResolver.class; this.blockOwnerStack.push(bLangMatchExpression); // Visit all pattern clauses if (bLangMatchExpression.patternClauses.isEmpty()) { this.isCursorWithinBlock(bLangMatchExpression.getPosition(), matchExprEnv); } bLangMatchExpression.getPatternClauses().forEach(patternClause -> { BLangNode node = patternClause; CursorPositionResolver posResolver = CursorPositionResolvers .getResolverByClass(cursorPositionResolver); posResolver.isCursorBeforeNode(node.getPosition(), node, visitor, visitor.lsContext); visitor.acceptNode(node, matchExprEnv); }); this.blockOwnerStack.pop(); this.cursorPositionResolver = fallbackCursorPositionResolver; } else { // We consider this as a special case and override the symbol environment node to be the match expression this.populateSymbolEnvNode(bLangMatchExpression); } } @Override public void visit(BLangMatchExpression.BLangMatchExprPatternClause matchExprPatternClause) { if (!CursorPositionResolvers.getResolverByClass(cursorPositionResolver).isCursorBeforeNode( matchExprPatternClause.getPosition(), matchExprPatternClause, this, this.lsContext)) { if (matchExprPatternClause.expr != null) { this.acceptNode(matchExprPatternClause.expr, symbolEnv); } } } @Override public void visit(BLangSimpleVarRef simpleVarRef) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver) .isCursorBeforeNode(simpleVarRef.getPosition(), simpleVarRef, this, this.lsContext); } @Override public void visit(BLangRecordLiteral recordLiteral) { SymbolEnv annotationAttachmentEnv = new SymbolEnv(recordLiteral, symbolEnv.scope); this.isCursorWithinBlock(recordLiteral.getPosition(), annotationAttachmentEnv); } @Override public void visit(BLangScope scopeNode) { CursorPositionResolver cpr = CursorPositionResolvers.getResolverByClass(cursorPositionResolver); if (cpr.isCursorBeforeNode(scopeNode.getPosition(), scopeNode, this, this.lsContext)) { return; } this.blockOwnerStack.push(scopeNode); this.acceptNode(scopeNode.scopeBody, symbolEnv); this.blockOwnerStack.pop(); this.acceptNode(scopeNode.compensationFunction, symbolEnv); } @Override public void visit(BLangCompensate node) { CursorPositionResolvers.getResolverByClass(cursorPositionResolver).isCursorBeforeNode(node.getPosition(), node, this, this.lsContext); } /////////////////////////////////// ///// Other Public Methods ///// /////////////////////////////////// /** * Resolve all visible symbols. * * @param symbolEnv symbol environment * @return all visible symbols for current scope */ public Map<Name, Scope.ScopeEntry> resolveAllVisibleSymbols(SymbolEnv symbolEnv) { return symbolResolver.getAllVisibleInScopeSymbols(symbolEnv); } /** * Populate the symbols. * * @param symbolEntries symbol entries * @param symbolEnv Symbol environment */ public void populateSymbols(Map<Name, Scope.ScopeEntry> symbolEntries, @Nonnull SymbolEnv symbolEnv) { List<SymbolInfo> visibleSymbols = new ArrayList<>(); this.populateSymbolEnvNode(symbolEnv.node); symbolEntries.forEach((k, v) -> visibleSymbols.add(new SymbolInfo(k.getValue(), v))); lsContext.put(CompletionKeys.VISIBLE_SYMBOLS_KEY, visibleSymbols); } public Stack<Node> getBlockOwnerStack() { return blockOwnerStack; } public Stack<BLangBlockStmt> getBlockStmtStack() { return blockStmtStack; } public SymbolEnv getSymbolEnv() { return symbolEnv; } public void setPreviousNode(BLangNode previousNode) { this.previousNode = previousNode; } public void setNextNode(BLangNode nextNode) { lsContext.put(CompletionKeys.NEXT_NODE_KEY, nextNode.getKind().toString().toLowerCase(Locale.ENGLISH)); } /** * Forcefully terminate the visitor and at the termination, populate the context data. */ public void forceTerminateVisitor() { lsContext.put(CompletionKeys.CURRENT_NODE_TRANSACTION_KEY, !this.isCurrentNodeTransactionStack.isEmpty()); lsContext.put(CompletionKeys.LOOP_COUNT_KEY, this.loopCount); lsContext.put(CompletionKeys.TRANSACTION_COUNT_KEY, this.transactionCount); lsContext.put(CompletionKeys.PREVIOUS_NODE_KEY, this.previousNode); if (!blockOwnerStack.isEmpty()) { lsContext.put(CompletionKeys.BLOCK_OWNER_KEY, blockOwnerStack.peek()); } this.terminateVisitor = true; } /////////////////////////////////// ///// Private Methods ///// /////////////////////////////////// private void acceptNode(BLangNode node, SymbolEnv env) { if (this.terminateVisitor) { return; } SymbolEnv prevEnv = this.symbolEnv; this.symbolEnv = env; node.accept(this); this.symbolEnv = prevEnv; this.setPreviousNode(node); } /** * Check whether the cursor is located within the given node's block scope. * * Note: This method should only be used to check and terminate the visitor when the content within the block * is empty. * * @param nodePosition Position of the current node * @param symbolEnv Symbol Environment * @return {@link Boolean} Whether the cursor within the block scope */ private boolean isCursorWithinBlock(DiagnosticPos nodePosition, @Nonnull SymbolEnv symbolEnv) { DiagnosticPos zeroBasedPosition = CommonUtil.toZeroBasedPosition(nodePosition); int line = lsContext.get(DocumentServiceKeys.POSITION_KEY).getPosition().getLine(); int nodeSLine = zeroBasedPosition.sLine; int nodeELine = zeroBasedPosition.eLine; if ((nodeSLine <= line && nodeELine >= line)) { Map<Name, Scope.ScopeEntry> visibleSymbolEntries = new HashMap<>(); if (symbolEnv.scope != null) { visibleSymbolEntries.putAll(this.resolveAllVisibleSymbols(symbolEnv)); } this.populateSymbols(visibleSymbolEntries, symbolEnv); this.forceTerminateVisitor(); return true; } return false; } /** * Check whether the cursor resides within the given node type's parameter context. * Node name is used to identify the correct node * * @param nodeName Name of the node * @param nodeType Node type (Function, Resource, Action or Connector) * @return {@link Boolean} Whether the cursor is within the parameter context */ private boolean isWithinParameterContext(String nodeName, String nodeType, SymbolEnv env) { ParserRuleContext parserRuleContext = lsContext.get(CompletionKeys.PARSER_RULE_CONTEXT_KEY); TokenStream tokenStream = lsContext.get(CompletionKeys.TOKEN_STREAM_KEY); String terminalToken = ""; // If the parser rule context is not parameter context or parameter list context, we skipp the calculation if (!(parserRuleContext instanceof BallerinaParser.ParameterContext || parserRuleContext instanceof BallerinaParser.ParameterListContext)) { return false; } int startTokenIndex = parserRuleContext.getStart().getTokenIndex(); ArrayList<String> terminalKeywords = new ArrayList<>( Arrays.asList(UtilSymbolKeys.ACTION_KEYWORD_KEY, UtilSymbolKeys.CONNECTOR_KEYWORD_KEY, UtilSymbolKeys.FUNCTION_KEYWORD_KEY, UtilSymbolKeys.RESOURCE_KEYWORD_KEY)); ArrayList<Token> filteredTokens = new ArrayList<>(); Token openBracket = null; boolean isWithinParams = false; // Find the index of the closing bracket while (true) { if (startTokenIndex > tokenStream.size()) { // In the ideal case, should not reach this point startTokenIndex = -1; break; } Token token = tokenStream.get(startTokenIndex); String tokenString = token.getText(); if (tokenString.equals(")")) { break; } startTokenIndex++; } // Backtrack the token stream to find a terminal token while (true) { if (startTokenIndex < 0) { break; } Token token = tokenStream.get(startTokenIndex); String tokenString = token.getText(); if (terminalKeywords.contains(tokenString)) { terminalToken = tokenString; break; } if (token.getChannel() == Token.DEFAULT_CHANNEL) { filteredTokens.add(token); } startTokenIndex--; } Collections.reverse(filteredTokens); /* This particular logic identifies a matching pair of closing and opening bracket and then check whether the cursor is within those bracket pair */ if (nodeName.equals(filteredTokens.get(0).getText()) && terminalToken.equals(nodeType)) { String tokenText; for (Token token : filteredTokens) { tokenText = token.getText(); if (tokenText.equals("(")) { openBracket = token; } else if (tokenText.equals(")") && openBracket != null) { Position cursorPos = lsContext.get(DocumentServiceKeys.POSITION_KEY).getPosition(); int openBLine = openBracket.getLine() - 1; int openBCol = openBracket.getCharPositionInLine(); int closeBLine = token.getLine() - 1; int closeBCol = token.getCharPositionInLine(); int cursorLine = cursorPos.getLine(); int cursorCol = cursorPos.getCharacter(); isWithinParams = (cursorLine > openBLine && cursorLine < closeBLine) || (cursorLine == openBLine && cursorCol > openBCol && cursorLine < closeBLine) || (cursorLine > openBLine && cursorCol < closeBCol && cursorLine == closeBLine) || (cursorLine == openBLine && cursorLine == closeBLine && cursorCol >= openBCol && cursorCol <= closeBCol); if (isWithinParams) { break; } else { openBracket = null; } } } } if (isWithinParams) { this.populateSymbols(this.resolveAllVisibleSymbols(env), env); forceTerminateVisitor(); } return isWithinParams; } private BLangVariableDef createVarDef(BLangVariable var) { BLangVariableDef varDefNode = new BLangVariableDef(); varDefNode.var = var; varDefNode.pos = var.pos; return varDefNode; } private BLangBlockStmt generateCodeBlock(StatementNode... statements) { BLangBlockStmt block = new BLangBlockStmt(); for (StatementNode stmt : statements) { block.addStatement(stmt); } return block; } private boolean isCursorAtResourceIdentifier(BLangResource bLangResource, LSContext context) { Position position = context.get(DocumentServiceKeys.POSITION_KEY).getPosition(); DiagnosticPos zeroBasedPo = CommonUtil.toZeroBasedPosition(bLangResource.getPosition()); int line = position.getLine(); int nodeSLine = zeroBasedPo.sLine; boolean status = line == nodeSLine; if (status) { forceTerminateVisitor(); } return status; } private void populateSymbolEnvNode(BLangNode node) { lsContext.put(CompletionKeys.SYMBOL_ENV_NODE_KEY, node); } private Predicate<Node> filterNodeByCompilationUnit(String cUnit) { return node -> node.getPosition().getSource().getCompilationUnitName().equals(cUnit); } }