/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on 19/07/2005 */ package com.python.pydev.analysis.visitors; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.IDocument; import org.python.pydev.core.IDefinition; import org.python.pydev.core.IModule; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.IToken; import org.python.pydev.core.callbacks.ICallbackListener; import org.python.pydev.core.log.Log; import org.python.pydev.core.structure.FastStack; import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken; import org.python.pydev.editor.codecompletion.revisited.visitors.AbstractVisitor; import org.python.pydev.editor.codecompletion.revisited.visitors.Definition; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Assert; import org.python.pydev.parser.jython.ast.Assign; import org.python.pydev.parser.jython.ast.AugAssign; import org.python.pydev.parser.jython.ast.Call; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.Compare; import org.python.pydev.parser.jython.ast.Comprehension; import org.python.pydev.parser.jython.ast.Expr; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.If; import org.python.pydev.parser.jython.ast.Lambda; import org.python.pydev.parser.jython.ast.ListComp; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.jython.ast.Print; import org.python.pydev.parser.jython.ast.Raise; import org.python.pydev.parser.jython.ast.Return; import org.python.pydev.parser.jython.ast.Str; import org.python.pydev.parser.jython.ast.While; import org.python.pydev.parser.jython.ast.Yield; import org.python.pydev.parser.jython.ast.decoratorsType; import com.aptana.shared_core.structure.Tuple; import com.python.pydev.analysis.IAnalysisPreferences; import com.python.pydev.analysis.messages.IMessage; import com.python.pydev.analysis.scopeanalysis.AbstractScopeAnalyzerVisitor; /** * This visitor marks the used/ unused tokens and generates the messages related * * @author Fabio */ public final class OccurrencesVisitor extends AbstractScopeAnalyzerVisitor { /** * Used to manage the messages */ public final MessagesManager messagesManager; /** * Used to check for duplication in signatures */ private final DuplicationChecker duplicationChecker; /** * Used to check if a signature from a method starts with self (if it is not a staticmethod) */ private final NoSelfChecker noSelfChecker; /** * Used to check arguments. */ private final ArgumentsChecker argumentsChecker; /** * Determines whether we should check if function call arguments actually match the signature of the object being * called. */ private final boolean analyzeArgumentsMismatch; public OccurrencesVisitor(IPythonNature nature, String moduleName, IModule current, IAnalysisPreferences prefs, IDocument document, IProgressMonitor monitor) { super(nature, moduleName, current, document, monitor); this.messagesManager = new MessagesManager(prefs, moduleName, document); this.analyzeArgumentsMismatch = prefs.getSeverityForType(IAnalysisPreferences.TYPE_ARGUMENTS_MISATCH) > IMarker.SEVERITY_INFO; //Don't even run checks if we don't raise at least a warning. if (this.analyzeArgumentsMismatch) { this.argumentsChecker = new ArgumentsChecker(this); } else { //Don't even create it if we're not going to use it. this.argumentsChecker = null; } this.duplicationChecker = new DuplicationChecker(this); this.noSelfChecker = new NoSelfChecker(this); } private int isInTestScope = 0; @Override public Object visitCompare(Compare node) throws Exception { Object ret = super.visitCompare(node); if (isInTestScope == 0) { SourceToken token = AbstractVisitor.makeToken(node, moduleName); messagesManager.addMessage(IAnalysisPreferences.TYPE_NO_EFFECT_STMT, token); } return ret; } public void traverse(If node) throws Exception { checkStop(); if (node.test != null) { isInTestScope += 1; node.test.accept(this); isInTestScope -= 1; } if (node.body != null) { for (int i = 0; i < node.body.length; i++) { if (node.body[i] != null) node.body[i].accept(this); } } if (node.orelse != null) { node.orelse.accept(this); } } @Override public Object visitTuple(org.python.pydev.parser.jython.ast.Tuple node) throws Exception { isInTestScope += 1; Object ret = super.visitTuple(node); isInTestScope -= 1; return ret; } public void traverse(While node) throws Exception { checkStop(); if (node.test != null) { isInTestScope += 1; node.test.accept(this); isInTestScope -= 1; } if (node.body != null) { for (int i = 0; i < node.body.length; i++) { if (node.body[i] != null) node.body[i].accept(this); } } if (node.orelse != null) node.orelse.accept(this); } @Override public Object visitRaise(Raise node) throws Exception { isInTestScope += 1; Object r = super.visitRaise(node); isInTestScope -= 1; return r; } @Override public Object visitComprehension(Comprehension node) throws Exception { isInTestScope += 1; Object r = super.visitComprehension(node); isInTestScope -= 1; return r; } @Override public Object visitAssert(Assert node) throws Exception { isInTestScope += 1; Object r = super.visitAssert(node); isInTestScope -= 1; return r; } @Override public Object visitPrint(Print node) throws Exception { isInTestScope += 1; Object r = super.visitPrint(node); isInTestScope -= 1; return r; } @Override public Object visitAssign(Assign node) throws Exception { isInTestScope += 1; Object r = super.visitAssign(node); isInTestScope -= 1; if (analyzeArgumentsMismatch) { argumentsChecker.visitAssign(node); } return r; } @Override public Object visitYield(Yield node) throws Exception { isInTestScope += 1; Object r = super.visitYield(node); isInTestScope -= 1; return r; } @Override public Object visitAugAssign(AugAssign node) throws Exception { isInTestScope += 1; Object r = super.visitAugAssign(node); isInTestScope -= 1; return r; } @Override public Object visitCall(Call node) throws Exception { isInTestScope += 1; Object r = super.visitCall(node); isInTestScope -= 1; return r; } @Override public Object visitReturn(Return node) throws Exception { isInTestScope += 1; Object r = super.visitReturn(node); isInTestScope -= 1; return r; } @Override protected void handleDecorator(decoratorsType dec) throws Exception { isInTestScope += 1; dec.accept(this); isInTestScope -= 1; } @Override public Object visitLambda(Lambda node) throws Exception { isInTestScope += 1; Object ret = super.visitLambda(node); isInTestScope -= 1; return ret; } public void traverse(SimpleNode node) throws Exception { if (node instanceof If) { traverse((If) node); } else if (node instanceof While) { traverse((While) node); } else if (node instanceof ListComp) { this.visitListComp((ListComp) node); } else { super.traverse(node); } } /** * @return the generated messages. */ public List<IMessage> getMessages() { endScope(null); //have to end the scope that started when we created the class. return messagesManager.getMessages(); } /** * @param foundTok */ protected void onAddUndefinedVarInImportMessage(IToken foundTok, Found foundAs) { messagesManager.addUndefinedVarInImportMessage(foundTok, foundTok.getRepresentation()); } /** * @param foundTok */ protected void onAddAssignmentToBuiltinMessage(IToken foundTok, String representation) { messagesManager.onAddAssignmentToBuiltinMessage(foundTok, representation); } /** * @param token */ protected void onAddUndefinedMessage(IToken token, Found foundAs) { if ("...".equals(token.getRepresentation())) { return; //Ellipsis -- when found in the grammar, it's added as a name, which we can safely ignore at this point. } //global scope, so, even if it is defined later, this is an error... messagesManager.addUndefinedMessage(token); } /** * @param m */ protected void onLastScope(ScopeItems m) { for (Found n : probablyNotDefined) { String rep = n.getSingle().tok.getRepresentation(); Map<String, Tuple<IToken, Found>> lastInStack = m.namesToIgnore; if (scope.findInNamesToIgnore(rep, lastInStack) == null) { onAddUndefinedMessage(n.getSingle().tok, n); } } } /** * @param reportUnused * @param m */ protected void onAfterEndScope(SimpleNode node, ScopeItems m) { boolean reportUnused = true; if (node != null && node instanceof FunctionDef) { reportUnused = !isVirtual((FunctionDef) node); } if (reportUnused) { //so, now, we clear the unused int scopeType = m.getScopeType(); for (List<Found> list : m.getAll()) { int len = list.size(); for (int i = 0; i < len; i++) { Found f = list.get(i); if (!f.isUsed()) { // we don't get unused at the global scope or class definition scope unless it's an import if ((scopeType & Scope.ACCEPTED_METHOD_AND_LAMBDA) != 0 || f.isImport()) { //only within methods do we put things as unused messagesManager.addUnusedMessage(node, f); } } } } } } /** * A method is virtual if it contains only raise and string statements */ protected boolean isVirtual(FunctionDef node) { if (node.body != null) { int len = node.body.length; for (int i = 0; i < len; i++) { SimpleNode n = node.body[i]; if (n instanceof Raise) { continue; } if (n instanceof Expr) { if (((Expr) n).value instanceof Str) { continue; } } return false; } } return true; } @Override protected void onAfterStartScope(int newScopeType, SimpleNode node) { if (newScopeType == Scope.SCOPE_TYPE_CLASS) { duplicationChecker.beforeClassDef((ClassDef) node); noSelfChecker.beforeClassDef((ClassDef) node); } else if ((newScopeType & Scope.SCOPE_TYPE_METHOD) != 0) { duplicationChecker.beforeFunctionDef((FunctionDef) node); //duplication checker noSelfChecker.beforeFunctionDef((FunctionDef) node); } } @Override protected void onBeforeEndScope(SimpleNode node) { if (node instanceof ClassDef) { noSelfChecker.afterClassDef((ClassDef) node); duplicationChecker.afterClassDef((ClassDef) node); } else if (node instanceof FunctionDef) { duplicationChecker.afterFunctionDef((FunctionDef) node);//duplication checker noSelfChecker.afterFunctionDef((FunctionDef) node); } } @Override public void onAddUnusedMessage(SimpleNode node, Found found) { messagesManager.addUnusedMessage(node, found); } @Override public void onAddReimportMessage(Found newFound) { messagesManager.addReimportMessage(newFound); } @Override public void onAddUnresolvedImport(IToken token) { messagesManager.addMessage(IAnalysisPreferences.TYPE_UNRESOLVED_IMPORT, token); } @Override protected void onAfterVisitAssign(Assign node) { noSelfChecker.visitAssign(node); } @Override protected void onVisitCallFunc(final Call callNode) throws Exception { if (!analyzeArgumentsMismatch) { super.onVisitCallFunc(callNode); } else { if (callNode.func instanceof Name) { Name name = (Name) callNode.func; startRecordFound(); visitName(name); //Check if the name was actually found in some way... TokenFoundStructure found = popFound(); if (found != null && found.token instanceof SourceToken) { final SourceToken sourceToken = (SourceToken) found.token; if (found.defined) { argumentsChecker.checkNameFound(callNode, sourceToken); } else if (found.found != null) { //Still not found: register a callback to be called if it's found later on. found.found.registerCallOnDefined(new ICallbackListener<Found>() { public Object call(Found f) { try { List<GenAndTok> all = f.getAll(); for (GenAndTok genAndTok : all) { if (genAndTok.tok instanceof SourceToken) { SourceToken sourceToken2 = (SourceToken) genAndTok.tok; if (sourceToken2.getAst() instanceof FunctionDef || sourceToken2.getAst() instanceof ClassDef) { argumentsChecker.checkNameFound(callNode, sourceToken2); return null; } } } } catch (Exception e) { Log.log(e); } return null; } }); } } } else { startRecordFound(); callNode.func.accept(this); TokenFoundStructure found = popFound(); argumentsChecker.checkAttrFound(callNode, found); } } } public static final class TokenFoundStructure { public final IToken token; public final boolean defined; public final Found found; /** * @param foundForProbablyNotDefined if not defined, the token used is passed on so that if it gets later defined, * a notification may be gotten. */ public TokenFoundStructure(IToken token, boolean defined, Found found) { this.token = token; this.defined = defined; this.found = found; } } private final FastStack<TokenFoundStructure> recordedFounds = new FastStack<TokenFoundStructure>(4); private int recordFounds = 0; private void onPushToRecordedFounds(IToken o1) { if (recordFounds > 0) { recordedFounds.push(new TokenFoundStructure(o1, true, null)); } } /** * Called when a token is not found. */ @Override protected void onAddToProbablyNotDefined(IToken token, Found foundForProbablyNotDefined) { if (recordFounds > 0) { recordedFounds.push(new TokenFoundStructure(token, false, foundForProbablyNotDefined)); } } /** * Gets the token which was found and whether it was actually defined at that time (otherwise, it may be that * it'll only be defined later on, in which case the check will have to be done later on too -- and only if it * was really defined). */ protected TokenFoundStructure popFound() { recordFounds -= 1; if (recordedFounds.size() > 0) { TokenFoundStructure ret = recordedFounds.peek(); recordedFounds.clear(); return ret; } return null; } protected void startRecordFound() { recordFounds += 1; } @Override protected void onFoundTokenAs(IToken token, Found foundAs) { if (analyzeArgumentsMismatch) { boolean reportFound = true; try { if (foundAs.importInfo != null) { IDefinition[] definition = foundAs.importInfo.getDefinitions(nature, completionCache); for (IDefinition iDefinition : definition) { Definition d = (Definition) iDefinition; if (d.ast instanceof FunctionDef || d.ast instanceof ClassDef) { SourceToken tok = AbstractVisitor.makeToken(d.ast, token.getRepresentation(), d.module != null ? d.module.getName() : ""); tok.setDefinition(d); onPushToRecordedFounds(tok); reportFound = false; break; } } } } catch (Exception e) { Log.log(e); } if (reportFound) { onPushToRecordedFounds(token); } } } @Override protected void onFoundInNamesToIgnore(IToken token, IToken tokenInNamesToIgnore) { if (analyzeArgumentsMismatch) { if (tokenInNamesToIgnore instanceof SourceToken) { SourceToken sourceToken = (SourceToken) tokenInNamesToIgnore; //Make a new token because we want the ast to be the FunctionDef or ClassDef, not the name which is the reference. onPushToRecordedFounds(AbstractVisitor.makeToken(sourceToken.getAst(), token.getRepresentation(), sourceToken.getParentPackage())); } } } }