/** * 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 24/07/2005 */ package com.python.pydev.analysis.visitors; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IMarker; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.python.pydev.core.FullRepIterable; import org.python.pydev.core.IToken; import org.python.pydev.core.docutils.ParsingUtils; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.docutils.SyntaxErrorException; import org.python.pydev.core.log.Log; 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.AbstractVisitor.ImportPartSourceToken; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Call; import org.python.pydev.parser.jython.ast.Expr; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.Import; import org.python.pydev.parser.jython.ast.ImportFrom; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.jython.ast.NameTok; import org.python.pydev.parser.jython.ast.Pass; import org.python.pydev.parser.jython.ast.Str; import org.python.pydev.parser.jython.ast.stmtType; import org.python.pydev.parser.visitors.NodeUtils; import com.aptana.shared_core.string.FastStringBuffer; import com.aptana.shared_core.structure.Tuple; import com.python.pydev.analysis.IAnalysisPreferences; import com.python.pydev.analysis.messages.CompositeMessage; import com.python.pydev.analysis.messages.IMessage; import com.python.pydev.analysis.messages.Message; public final class MessagesManager { /** * preferences for indicating the severities */ private final IAnalysisPreferences prefs; /** * this map should hold the generator source token and the messages that are generated for it */ public final Map<IToken, List<IMessage>> messages = new HashMap<IToken, List<IMessage>>(); public final List<IMessage> independentMessages = new ArrayList<IMessage>(); /** * Should be used to give the name of the module we are visiting */ private final String moduleName; /** * This is the document */ private final IDocument document; public MessagesManager(IAnalysisPreferences prefs, String moduleName, IDocument doc) { this.prefs = prefs; this.moduleName = moduleName; this.document = doc; } /** * @return whether we should add an unused import message to the module being analyzed */ public boolean shouldAddUnusedImportMessage() { if (moduleName == null) { return true; } String onlyModName = FullRepIterable.headAndTail(moduleName, true)[1]; Set<String> patternsToBeIgnored = this.prefs.getModuleNamePatternsToBeIgnored(); for (String pattern : patternsToBeIgnored) { if (onlyModName.matches(pattern)) { return false; } } return true; } /** * adds a message of some type given its formatting params */ public void addMessage(int type, IToken generator, Object... objects) { if (isUnusedImportMessage(type)) { if (!shouldAddUnusedImportMessage()) { return; } } doAddMessage(independentMessages, type, objects, generator); } /** * @param type the type of the message * @return whether it is an unused import message */ private boolean isUnusedImportMessage(int type) { return type == IAnalysisPreferences.TYPE_UNUSED_IMPORT || type == IAnalysisPreferences.TYPE_UNUSED_WILD_IMPORT; } /** * adds a message of some type for a given token */ public void addMessage(int type, IToken token) { List<IMessage> msgs = getMsgsList(token); doAddMessage(msgs, type, token.getRepresentation(), token); } /** * checks if the message should really be added and does the add. */ private void doAddMessage(List<IMessage> msgs, int type, Object string, IToken token) { if (isUnusedImportMessage(type)) { if (!shouldAddUnusedImportMessage()) { return; } } Message messageToAdd = new Message(type, string, token, prefs); doAddMessage(msgs, messageToAdd); } private void doAddMessage(List<IMessage> msgs, Message messageToAdd) { String messageToIgnore = prefs.getRequiredMessageToIgnore(messageToAdd.getType()); if (messageToIgnore != null) { int startLine = messageToAdd.getStartLine(document) - 1; String line = PySelection.getLine(document, startLine); if (line.indexOf(messageToIgnore) != -1) { //keep going... nothing to see here... return; } } msgs.add(messageToAdd); } /** * adds a message of some type for some Found instance */ public void addMessage(int type, IToken generator, IToken tok) { addMessage(type, generator, tok, tok.getRepresentation()); } /** * adds a message of some type for some Found instance */ public void addMessage(int type, IToken generator, IToken tok, String rep) { List<IMessage> msgs = getMsgsList(generator); doAddMessage(msgs, type, rep, generator); } /** * @return the messages associated with a token */ public List<IMessage> getMsgsList(IToken generator) { List<IMessage> msgs = messages.get(generator); if (msgs == null) { msgs = new ArrayList<IMessage>(); messages.put(generator, msgs); } return msgs; } public void addUndefinedMessage(IToken token) { addUndefinedMessage(token, null); } /** * @param token adds a message saying that a token is not defined */ public void addUndefinedMessage(IToken token, String rep) { Tuple<Boolean, String> undef = isActuallyUndefined(token, rep); if (undef.o1) { addMessage(IAnalysisPreferences.TYPE_UNDEFINED_VARIABLE, token, undef.o2); } } /** * @param token adds a message saying that a token gathered from an import is not defined */ public void addUndefinedVarInImportMessage(IToken token, String rep) { Tuple<Boolean, String> undef = isActuallyUndefined(token, rep); if (undef.o1) { addMessage(IAnalysisPreferences.TYPE_UNDEFINED_IMPORT_VARIABLE, token, undef.o2); } } /** * @param token adds a message saying that a token gathered from assignment is a reserved keyword */ public void onAddAssignmentToBuiltinMessage(IToken token, String rep) { addMessage(IAnalysisPreferences.TYPE_ASSIGNMENT_TO_BUILT_IN_SYMBOL, token); } /** * Checks if some token is actually undefined and changes its representation if needed * @return a tuple indicating if it really is undefined and the representation that should be used. */ protected Tuple<Boolean, String> isActuallyUndefined(IToken token, String rep) { String tokenRepresentation = token.getRepresentation(); if (tokenRepresentation != null) { String firstPart = FullRepIterable.getFirstPart(tokenRepresentation); if (this.prefs.getTokensAlwaysInGlobals().contains(firstPart)) { return new Tuple<Boolean, String>(false, firstPart); //ok firstPart in not really undefined... } } boolean isActuallyUndefined = true; if (rep == null) { rep = tokenRepresentation; } int i; if ((i = rep.indexOf('.')) != -1) { rep = rep.substring(0, i); } String builtinType = NodeUtils.getBuiltinType(rep); if (builtinType != null) { isActuallyUndefined = false; //this is a builtin, so, it is defined after all } return new Tuple<Boolean, String>(isActuallyUndefined, rep); } public void onArgumentsMismatch(IToken token, Call callNode) { FastStringBuffer buf = new FastStringBuffer(128); buf.append(token.getRepresentation()); buf.append(": arguments don't match"); List<IMessage> msgs = getMsgsList(token); //Code that'll gather the position of the start/end parenthesis and will create a message at that location //(otherwise, it'd create the message at the name location, which may be a bit confusing). ParsingUtils parsingUtils = ParsingUtils.create(document); try { int offset = PySelection.getAbsoluteCursorOffset(document, callNode.func.beginLine - 1, callNode.func.beginColumn - 1); //-1: from ast to document coords int openParensPos = parsingUtils.findNextChar(offset, '('); if (openParensPos != -1) { int closeParensPos = parsingUtils.eatPar(openParensPos, null); if (closeParensPos != -1) { int startLine = PySelection.getLineOfOffset(document, openParensPos) + 1; //+1: from document to ast int endLine = PySelection.getLineOfOffset(document, closeParensPos) + 1; int startCol = openParensPos - document.getLineInformationOfOffset(openParensPos).getOffset() + 1; //+1 doc to ast +1 because we also want to get the closing ')' char. int endCol = closeParensPos - document.getLineInformationOfOffset(closeParensPos).getOffset() + 1 + 1; Message messageToAdd = new Message(IAnalysisPreferences.TYPE_ARGUMENTS_MISATCH, buf.toString(), startLine, endLine, startCol, endCol, prefs); doAddMessage(msgs, messageToAdd); return; } } } catch (BadLocationException e) { Log.log(e); } catch (SyntaxErrorException e) { //Just ignore } //If some error happened getting the parens position, just add it to the name. doAddMessage(msgs, IAnalysisPreferences.TYPE_ARGUMENTS_MISATCH, buf.toString(), token); } /** * adds a message for something that was not used * * @param node the node representing the scope being closed when adding the * unused message */ public void addUnusedMessage(SimpleNode node, Found f) { List<GenAndTok> all = f.getAll(); int len = all.size(); for (int i = 0; i < len; i++) { GenAndTok g = all.get(i); if (g.generator instanceof SourceToken) { SimpleNode ast = ((SourceToken) g.generator).getAst(); // it can be an unused import boolean isFromImport = ast instanceof ImportFrom; if (isFromImport || ast instanceof Import) { if (isFromImport && AbstractVisitor.isWildImport((ImportFrom) ast)) { addMessage(IAnalysisPreferences.TYPE_UNUSED_WILD_IMPORT, g.generator, g.tok); } else if (!(g.generator instanceof ImportPartSourceToken)) { addMessage(IAnalysisPreferences.TYPE_UNUSED_IMPORT, g.generator, g.tok); } continue; // finish it... } } // or unused variable // we have to check if this is a name we should ignore if (startsWithNamesToIgnore(g)) { int type = IAnalysisPreferences.TYPE_UNUSED_VARIABLE; if (g.tok instanceof SourceToken) { SourceToken t = (SourceToken) g.tok; SimpleNode ast = t.getAst(); if (ast instanceof NameTok) { NameTok n = (NameTok) ast; if (n.ctx == NameTok.KwArg || n.ctx == NameTok.VarArg || n.ctx == NameTok.KeywordName) { type = IAnalysisPreferences.TYPE_UNUSED_PARAMETER; } } else if (ast instanceof Name) { Name n = (Name) ast; if (n.ctx == Name.Param || n.ctx == Name.KwOnlyParam) { type = IAnalysisPreferences.TYPE_UNUSED_PARAMETER; } } } boolean addMessage = true; if (type == IAnalysisPreferences.TYPE_UNUSED_PARAMETER) { // just add unused parameters in methods that have some content (not only 'pass' and 'strings') if (node instanceof FunctionDef) { addMessage = false; FunctionDef def = (FunctionDef) node; for (stmtType b : def.body) { if (b instanceof Pass) { continue; } if (b instanceof Expr) { Expr expr = (Expr) b; if (expr.value instanceof Str) { continue; } } addMessage = true; break; } } }//END if (type == IAnalysisPreferences.TYPE_UNUSED_PARAMETER) if (addMessage) { addMessage(type, g.generator, g.tok); } } } } /** * a cache, so that we don't get the names to ignore over and over this is * ok, because every time we start an analysis session, this object is * re-created, and the options will not change all the time */ private Set<String> namesToIgnoreCache = null; /** * @param g the generater that will generate an unused variable message * @return true if we should not add the message */ private boolean startsWithNamesToIgnore(GenAndTok g) { if (namesToIgnoreCache == null) { namesToIgnoreCache = prefs.getNamesIgnoredByUnusedVariable(); } String representation = g.tok.getRepresentation(); boolean addIt = true; for (String str : namesToIgnoreCache) { if (representation.startsWith(str)) { addIt = false; break; } } return addIt; } /** * adds a message for a re-import */ public void addReimportMessage(Found f) { List<GenAndTok> all = f.getAll(); int len = all.size(); for (int i = 0; i < len; i++) { GenAndTok g = all.get(i); //we don't want to add reimport messages if they are found in a wild import if (g.generator instanceof SourceToken && !(g.generator instanceof ImportPartSourceToken) && g.generator.isWildImport() == false) { addMessage(IAnalysisPreferences.TYPE_REIMPORT, g.generator, g.tok); } } } /** * @return the generated messages. */ public List<IMessage> getMessages() { List<IMessage> result = new ArrayList<IMessage>(); //let's get the messages for (List<IMessage> l : messages.values()) { if (l.size() < 1) { //we need at least one message continue; } Map<Integer, List<IMessage>> messagesByType = getMessagesByType(l); for (int type : messagesByType.keySet()) { l = messagesByType.get(type); //the values are guaranteed to have size at least equal to 1 IMessage message = l.get(0); //messages are grouped by type, and the severity is set by type, so, this is ok... if (message.getSeverity() == IMarker.SEVERITY_INFO) { if (doIgnoreMessageIfJustInformational(message.getType())) { //ok, let's ignore it for real (and don't add it) as those are not likely to be //used anyways for other actions) continue; } } //we add even ignore messages because they might be used later in actions dependent on code analysis if (l.size() == 1) { //don't add additional info: not being used // addAdditionalInfoToUnusedWildImport(message); addToResult(result, message); } else { //the generator token has many associated messages - the messages may have different types, //so, we need to get them by types IToken generator = message.getGenerator(); CompositeMessage compositeMessage; if (generator != null) { compositeMessage = new CompositeMessage(message.getType(), generator, prefs); } else { compositeMessage = new CompositeMessage(message.getType(), message.getStartLine(document), message.getEndLine(document), message.getStartCol(document), message.getEndCol(document), prefs); } for (IMessage m : l) { compositeMessage.addMessage(m); } //don't add additional info: not being used // addAdditionalInfoToUnusedWildImport(compositeMessage); addToResult(result, compositeMessage); } } } for (IMessage message : independentMessages) { if (message.getSeverity() == IMarker.SEVERITY_INFO) { if (doIgnoreMessageIfJustInformational(message.getType())) { //ok, let's ignore it for real (and don't add it) as those are not likely to be //used anyways for other actions) continue; } //otherwise keep on and add it (needed for some actions) } addToResult(result, message); } return result; } private boolean doIgnoreMessageIfJustInformational(int type) { return type == IAnalysisPreferences.TYPE_UNUSED_PARAMETER || type == IAnalysisPreferences.TYPE_INDENTATION_PROBLEM || type == IAnalysisPreferences.TYPE_NO_EFFECT_STMT || type == IAnalysisPreferences.TYPE_PEP8 || type == IAnalysisPreferences.TYPE_ASSIGNMENT_TO_BUILT_IN_SYMBOL || type == IAnalysisPreferences.TYPE_ARGUMENTS_MISATCH; } /** * @param result * @param message */ private void addToResult(List<IMessage> result, IMessage message) { if (isUnusedImportMessage(message.getType())) { IToken generator = message.getGenerator(); if (generator instanceof SourceToken) { String asAbsoluteImport = generator.getAsAbsoluteImport(); if (asAbsoluteImport.indexOf("__future__.") != -1 || asAbsoluteImport.indexOf("__metaclass__") != -1) { //do not add from __future__ import xxx return; } } } result.add(message); } // Comented out: we're not using this info (so, let's save some memory until we really need that) // /** // * @param message the message to which we will add additional info // */ // private void addAdditionalInfoToUnusedWildImport(IMessage message) { // if(message.getType() == IAnalysisPreferences.TYPE_UNUSED_WILD_IMPORT){ // // //we have to add additional info on it, saying which tokens where used // if(AbstractVisitor.isWildImport(message.getGenerator())){ // // List<Tuple<String,Found>> usedItems = lastScope.getUsedItems(); // for (Tuple<String, Found> tuple : usedItems) { // if(tuple.o2.getSingle().generator == message.getGenerator()){ // message.addAdditionalInfo(tuple.o1); // } // } // } // } // } /** * @return a map with the messages separated by type (keys are the type) * * the values are guaranteed to have size at least equal to 1 */ private Map<Integer, List<IMessage>> getMessagesByType(List<IMessage> l) { HashMap<Integer, List<IMessage>> messagesByType = new HashMap<Integer, List<IMessage>>(); for (IMessage message : l) { List<IMessage> messages = messagesByType.get(message.getType()); if (messages == null) { messages = new ArrayList<IMessage>(); messagesByType.put(message.getType(), messages); } messages.add(message); } return messagesByType; } }