Commit 0ed82476 authored by Christian Kamm's avatar Christian Kamm
Browse files

QmlJS checks: Suppression of static analysis messages.



* Use '// @disable M123' to suppress
* Add quick fix to add these comments

Change-Id: I499f5a6004148afbb346218c6fb01c9ca77b9f73
Reviewed-by: default avatarRoberto Raggi <roberto.raggi@nokia.com>
parent cf156cd2
......@@ -1136,10 +1136,42 @@ void Check::addMessages(const QList<Message> &messages)
addMessage(msg);
}
static bool hasOnlySpaces(const QString &s)
{
for (int i = 0; i < s.size(); ++i)
if (!s.at(i).isSpace())
return false;
return true;
}
void Check::addMessage(const Message &message)
{
if (message.isValid() && _enabledMessages.contains(message.type))
if (message.isValid() && _enabledMessages.contains(message.type)) {
// check for 'ignore this message'-type comments
const QString &suppressMessage = message.suppressionString();
foreach (const SourceLocation &commentLoc, _doc->engine()->comments()) {
if (commentLoc.startLine > message.location.startLine)
break;
if (commentLoc.startLine < message.location.startLine - 1)
continue;
// only look at comments on the previous line if there's only spaces before the comment
// note: startColumn is 1-based and *after* the starting // or /*
if (commentLoc.startLine == message.location.startLine - 1
&& commentLoc.startColumn > 3) {
const QString &beforeComment = _doc->source().mid(commentLoc.begin() - commentLoc.startColumn + 1,
commentLoc.startColumn - 3);
if (!hasOnlySpaces(beforeComment))
continue;
}
const QString &comment = _doc->source().mid(commentLoc.begin(), commentLoc.length);
if (comment.contains(suppressMessage))
return;
}
_messages += message;
}
}
void Check::addMessage(Type type, const SourceLocation &location, const QString &arg1, const QString &arg2)
......
......@@ -218,6 +218,7 @@ Message::Message(Type type, AST::SourceLocation location, const QString &arg1, c
qWarning() << "StaticAnalysis message" << type << "expects exactly two arguments";
message = message.arg(arg1, arg2);
}
message.append(QString(" (M%1)").arg(QString::number(prototype.type)));
}
bool Message::isValid() const
......@@ -242,3 +243,8 @@ DiagnosticMessage Message::toDiagnosticMessage() const
diagnostic.message = message;
return diagnostic;
}
QString Message::suppressionString() const
{
return QString("@disable M%1").arg(QString::number(type));
}
......@@ -122,6 +122,8 @@ public:
bool isValid() const;
DiagnosticMessage toDiagnosticMessage() const;
QString suppressionString() const;
AST::SourceLocation location;
QString message;
Type type;
......
......@@ -851,6 +851,45 @@ static void appendExtraSelectionsForMessages(
}
}
static void appendExtraSelectionsForMessages(
QList<QTextEdit::ExtraSelection> *selections,
const QList<StaticAnalysis::Message> &messages,
const QTextDocument *document)
{
foreach (const StaticAnalysis::Message &d, messages) {
const int line = d.location.startLine;
const int column = qMax(1U, d.location.startColumn);
QTextEdit::ExtraSelection sel;
QTextCursor c(document->findBlockByNumber(line - 1));
sel.cursor = c;
sel.cursor.setPosition(c.position() + column - 1);
if (d.location.length == 0) {
if (sel.cursor.atBlockEnd())
sel.cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor);
else
sel.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
} else {
sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d.location.length);
}
if (d.severity == StaticAnalysis::Warning || d.severity == StaticAnalysis::MaybeWarning) {
sel.format.setUnderlineColor(Qt::darkYellow);
} else if (d.severity == StaticAnalysis::Error || d.severity == StaticAnalysis::MaybeError) {
sel.format.setUnderlineColor(Qt::red);
} else if (d.severity == StaticAnalysis::Hint) {
sel.format.setUnderlineColor(Qt::darkGreen);
}
sel.format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
sel.format.setToolTip(d.message);
selections->append(sel);
}
}
void QmlJSTextEditorWidget::onDocumentUpdated(QmlJS::Document::Ptr doc)
{
if (file()->fileName() != doc->fileName()
......@@ -1543,6 +1582,7 @@ void QmlJSTextEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
QList<QTextEdit::ExtraSelection> selections;
appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document());
appendExtraSelectionsForMessages(&selections, m_semanticInfo.semanticMessages, document());
appendExtraSelectionsForMessages(&selections, m_semanticInfo.staticAnalysisMessages, document());
setExtraSelections(CodeWarningsSelection, selections);
Core::EditorManager *editorManager = Core::EditorManager::instance();
......
......@@ -38,6 +38,7 @@
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsscanner.h>
#include <qmljs/qmljsscopechain.h>
#include <qmljs/qmljsstaticanalysismessage.h>
#include <texteditor/basetexteditor.h>
#include <texteditor/quickfix.h>
......@@ -136,6 +137,7 @@ public: // attributes
// these are in addition to the parser messages in the document
QList<QmlJS::DiagnosticMessage> semanticMessages;
QList<QmlJS::StaticAnalysis::Message> staticAnalysisMessages;
private:
QSharedPointer<const QmlJS::ScopeChain> m_rootScopeChain;
......
......@@ -133,6 +133,53 @@ private:
};
};
/*
Adds a comment to suppress a static analysis message
*/
class AddAnalysisMessageSuppressionComment: public QmlJSQuickFixFactory
{
public:
virtual QList<QmlJSQuickFixOperation::Ptr> match(
const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface)
{
const QList<StaticAnalysis::Message> &messages = interface->semanticInfo().staticAnalysisMessages;
foreach (const StaticAnalysis::Message &message, messages) {
if (interface->currentFile()->isCursorOn(message.location)) {
return singleResult(new Operation(interface, message));
}
}
return noResult();
}
private:
class Operation: public QmlJSQuickFixOperation
{
StaticAnalysis::Message _message;
public:
Operation(const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface,
const StaticAnalysis::Message &message)
: QmlJSQuickFixOperation(interface, 0)
, _message(message)
{
setDescription(AddAnalysisMessageSuppressionComment::tr("Add a comment to suppress this message"));
}
virtual void performChanges(QmlJSRefactoringFilePtr currentFile,
const QmlJSRefactoringChanges &)
{
Utils::ChangeSet changes;
const int insertLoc = _message.location.begin() - _message.location.startColumn + 1;
changes.insert(insertLoc, QString("// %1\n").arg(_message.suppressionString()));
currentFile->setChangeSet(changes);
currentFile->appendIndentRange(Range(insertLoc, insertLoc + 1));
currentFile->apply();
}
};
};
} // end of anonymous namespace
void registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
......@@ -140,4 +187,5 @@ void registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
plugIn->addAutoReleasedObject(new SplitInitializerOp);
plugIn->addAutoReleasedObject(new ComponentFromObjectDef);
plugIn->addAutoReleasedObject(new WrapInLoader);
plugIn->addAutoReleasedObject(new AddAnalysisMessageSuppressionComment);
}
......@@ -148,9 +148,7 @@ SemanticInfo SemanticInfoUpdater::semanticInfo(const SemanticInfoUpdaterSource &
if (doc->language() != QmlJS::Document::JsonLanguage) {
QmlJS::Check checker(doc, semanticInfo.context);
foreach (const QmlJS::StaticAnalysis::Message &msg, checker()) {
semanticInfo.semanticMessages += msg.toDiagnosticMessage();
}
semanticInfo.staticAnalysisMessages = checker();
}
return semanticInfo;
......
......@@ -167,6 +167,12 @@ bool QmlJSRefactoringFile::isCursorOn(AST::UiQualifiedId *ast) const
return pos <= ast->identifierToken.end();
}
bool QmlJSRefactoringFile::isCursorOn(AST::SourceLocation loc) const
{
const unsigned pos = cursor().position();
return pos >= loc.begin() && pos <= loc.end();
}
QmlJSRefactoringChangesData *QmlJSRefactoringFile::data() const
{
return static_cast<QmlJSRefactoringChangesData *>(m_data.data());
......
......@@ -63,6 +63,7 @@ public:
bool isCursorOn(QmlJS::AST::UiObjectMember *ast) const;
bool isCursorOn(QmlJS::AST::UiQualifiedId *ast) const;
bool isCursorOn(QmlJS::AST::SourceLocation loc) const;
protected:
QmlJSRefactoringFile(const QString &fileName, const QSharedPointer<TextEditor::RefactoringChangesData> &data);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment