Commit b671baef authored by Christian Kamm's avatar Christian Kamm
Browse files

QmlJS: Improve completion and hints for functions.



* FunctionValues know about optional arguments (for builtins)
* ASTFunctionValues only report themselves as variadic if they
  use the 'arguments' array.
* Function argument hint shows optional args and variadic.
* Completion automatically adds parentheses.

Change-Id: Ib2598600ff8b1ce8c5de3bcabd24a3e171ff3a57
Reviewed-by: default avatarRoberto Raggi <roberto.raggi@nokia.com>
parent 8e236db9
......@@ -152,7 +152,7 @@ public:
{
}
virtual int argumentCount() const
virtual int namedArgumentCount() const
{
return _method.parameterNames().size();
}
......@@ -1219,7 +1219,7 @@ const Value *FunctionValue::returnValue() const
return valueOwner()->unknownValue();
}
int FunctionValue::argumentCount() const
int FunctionValue::namedArgumentCount() const
{
return 0;
}
......@@ -1234,6 +1234,11 @@ QString FunctionValue::argumentName(int index) const
return QString::fromLatin1("arg%1").arg(index + 1);
}
int FunctionValue::optionalNamedArgumentCount() const
{
return 0;
}
bool FunctionValue::isVariadic() const
{
return true;
......@@ -1255,7 +1260,10 @@ void FunctionValue::accept(ValueVisitor *visitor) const
}
Function::Function(ValueOwner *valueOwner)
: FunctionValue(valueOwner), _returnValue(0)
: FunctionValue(valueOwner)
, _returnValue(0)
, _optionalNamedArgumentCount(0)
, _isVariadic(false)
{
setClassName("Function");
}
......@@ -1284,11 +1292,26 @@ void Function::setReturnValue(const Value *returnValue)
_returnValue = returnValue;
}
int Function::argumentCount() const
void Function::setVariadic(bool variadic)
{
_isVariadic = variadic;
}
void Function::setOptionalNamedArgumentCount(int count)
{
_optionalNamedArgumentCount = count;
}
int Function::namedArgumentCount() const
{
return _arguments.size();
}
int Function::optionalNamedArgumentCount() const
{
return _optionalNamedArgumentCount;
}
const Value *Function::argument(int index) const
{
return _arguments.at(index);
......@@ -1309,6 +1332,11 @@ const Value *Function::invoke(const Activation *) const
return _returnValue;
}
bool Function::isVariadic() const
{
return _isVariadic;
}
////////////////////////////////////////////////////////////////////////////////
// typing environment
////////////////////////////////////////////////////////////////////////////////
......@@ -1879,13 +1907,47 @@ bool ASTVariableReference::getSourceLocation(QString *fileName, int *line, int *
return true;
}
namespace {
class UsesArgumentsArray : protected Visitor
{
bool _usesArgumentsArray;
public:
bool operator()(FunctionBody *ast)
{
if (!ast || !ast->elements)
return false;
_usesArgumentsArray = false;
Node::accept(ast->elements, this);
return _usesArgumentsArray;
}
protected:
bool visit(ArrayMemberExpression *ast)
{
if (IdentifierExpression *idExp = cast<IdentifierExpression *>(ast->base)) {
if (idExp->name == QLatin1String("arguments"))
_usesArgumentsArray = true;
}
return true;
}
// don't go into nested functions
bool visit(FunctionBody *) { return false; }
};
} // anonymous namespace
ASTFunctionValue::ASTFunctionValue(FunctionExpression *ast, const Document *doc, ValueOwner *valueOwner)
: FunctionValue(valueOwner), _ast(ast), _doc(doc)
: FunctionValue(valueOwner)
, _ast(ast)
, _doc(doc)
{
setPrototype(valueOwner->functionPrototype());
for (FormalParameterList *it = ast->formals; it; it = it->next)
_argumentNames.append(it->name.toString());
_isVariadic = UsesArgumentsArray()(ast->body);
}
ASTFunctionValue::~ASTFunctionValue()
......@@ -1897,7 +1959,7 @@ FunctionExpression *ASTFunctionValue::ast() const
return _ast;
}
int ASTFunctionValue::argumentCount() const
int ASTFunctionValue::namedArgumentCount() const
{
return _argumentNames.size();
}
......@@ -1913,6 +1975,11 @@ QString ASTFunctionValue::argumentName(int index) const
return FunctionValue::argumentName(index);
}
bool ASTFunctionValue::isVariadic() const
{
return _isVariadic;
}
bool ASTFunctionValue::getSourceLocation(QString *fileName, int *line, int *column) const
{
*fileName = _doc->fileName();
......@@ -2021,7 +2088,7 @@ const ASTSignal *ASTSignal::asAstSignal() const
return this;
}
int ASTSignal::argumentCount() const
int ASTSignal::namedArgumentCount() const
{
int count = 0;
for (UiParameterList *it = _ast->parameters; it; it = it->next)
......
......@@ -583,11 +583,24 @@ public:
virtual const Value *returnValue() const;
virtual int argumentCount() const;
virtual const Value *argument(int index) const;
// Access to the names of arguments
// Named arguments can be optional (usually known for builtins only)
virtual int namedArgumentCount() const;
virtual QString argumentName(int index) const;
// The number of optional named arguments
// Example: JSON.stringify(value[, replacer[, space]])
// has namedArgumentCount = 3
// and optionalNamedArgumentCount = 2
virtual int optionalNamedArgumentCount() const;
// Whether the function accepts an unlimited number of arguments
// after the named ones. Defaults to false.
// Example: Math.max(...)
virtual bool isVariadic() const;
virtual const Value *argument(int index) const;
virtual const Value *invoke(const Activation *activation) const;
// Value interface
......@@ -603,18 +616,24 @@ public:
void addArgument(const Value *argument, const QString &name = QString());
void setReturnValue(const Value *returnValue);
void setVariadic(bool variadic);
void setOptionalNamedArgumentCount(int count);
// FunctionValue interface
virtual const Value *returnValue() const;
virtual int argumentCount() const;
virtual int namedArgumentCount() const;
virtual int optionalNamedArgumentCount() const;
virtual const Value *argument(int index) const;
virtual QString argumentName(int index) const;
virtual const Value *invoke(const Activation *activation) const;
virtual bool isVariadic() const;
private:
ValueList _arguments;
QStringList _argumentNames;
const Value *_returnValue;
int _optionalNamedArgumentCount;
bool _isVariadic;
};
......@@ -799,6 +818,7 @@ class QMLJS_EXPORT ASTFunctionValue: public FunctionValue
AST::FunctionExpression *_ast;
const Document *_doc;
QList<QString> _argumentNames;
bool _isVariadic;
public:
ASTFunctionValue(AST::FunctionExpression *ast, const Document *doc, ValueOwner *valueOwner);
......@@ -806,8 +826,9 @@ public:
AST::FunctionExpression *ast() const;
virtual int argumentCount() const;
virtual int namedArgumentCount() const;
virtual QString argumentName(int index) const;
virtual bool isVariadic() const;
virtual bool getSourceLocation(QString *fileName, int *line, int *column) const;
};
......@@ -851,7 +872,7 @@ public:
const ObjectValue *bodyScope() const { return _bodyScope; }
// FunctionValue interface
virtual int argumentCount() const;
virtual int namedArgumentCount() const;
virtual const Value *argument(int index) const;
virtual QString argumentName(int index) const;
......
......@@ -499,21 +499,20 @@ QString ValueOwner::typeId(const Value *value)
return _typeId(value);
}
Function *ValueOwner::addFunction(ObjectValue *object, const QString &name, const Value *result, int argumentCount)
Function *ValueOwner::addFunction(ObjectValue *object, const QString &name, const Value *result, int argumentCount, int optionalCount, bool variadic)
{
Function *function = newFunction();
Function *function = addFunction(object, name, argumentCount, optionalCount, variadic);
function->setReturnValue(result);
for (int i = 0; i < argumentCount; ++i)
function->addArgument(unknownValue());
object->setMember(name, function);
return function;
}
Function *ValueOwner::addFunction(ObjectValue *object, const QString &name, int argumentCount)
Function *ValueOwner::addFunction(ObjectValue *object, const QString &name, int argumentCount, int optionalCount, bool variadic)
{
Function *function = newFunction();
for (int i = 0; i < argumentCount; ++i)
function->addArgument(unknownValue());
function->setVariadic(variadic);
function->setOptionalNamedArgumentCount(optionalCount);
object->setMember(name, function);
return function;
}
......@@ -538,46 +537,58 @@ void ValueOwner::initializePrototypes()
_objectCtor->setPrototype(_functionPrototype);
_objectCtor->setMember("prototype", _objectPrototype);
_objectCtor->setReturnValue(newObject());
_objectCtor->addArgument(unknownValue(), "value");
_objectCtor->setOptionalNamedArgumentCount(1);
_functionCtor = new FunctionCtor(this);
_functionCtor->setPrototype(_functionPrototype);
_functionCtor->setMember("prototype", _functionPrototype);
_functionCtor->setReturnValue(newFunction());
_functionCtor->setVariadic(true);
_arrayCtor = new ArrayCtor(this);
_arrayCtor->setPrototype(_functionPrototype);
_arrayCtor->setMember("prototype", _arrayPrototype);
_arrayCtor->setReturnValue(newArray());
_arrayCtor->setVariadic(true);
_stringCtor = new StringCtor(this);
_stringCtor->setPrototype(_functionPrototype);
_stringCtor->setMember("prototype", _stringPrototype);
_stringCtor->setReturnValue(stringValue());
_stringCtor->addArgument(unknownValue(), "value");
_stringCtor->setOptionalNamedArgumentCount(1);
_booleanCtor = new BooleanCtor(this);
_booleanCtor->setPrototype(_functionPrototype);
_booleanCtor->setMember("prototype", _booleanPrototype);
_booleanCtor->setReturnValue(booleanValue());
_booleanCtor->addArgument(unknownValue(), "value");
_numberCtor = new NumberCtor(this);
_numberCtor->setPrototype(_functionPrototype);
_numberCtor->setMember("prototype", _numberPrototype);
_numberCtor->setReturnValue(numberValue());
_numberCtor->addArgument(unknownValue(), "value");
_numberCtor->setOptionalNamedArgumentCount(1);
_dateCtor = new DateCtor(this);
_dateCtor->setPrototype(_functionPrototype);
_dateCtor->setMember("prototype", _datePrototype);
_dateCtor->setReturnValue(_datePrototype);
_dateCtor->setVariadic(true);
_regexpCtor = new RegExpCtor(this);
_regexpCtor->setPrototype(_functionPrototype);
_regexpCtor->setMember("prototype", _regexpPrototype);
_regexpCtor->setReturnValue(_regexpPrototype);
_regexpCtor->addArgument(unknownValue(), "pattern");
_regexpCtor->addArgument(unknownValue(), "flags");
addFunction(_objectCtor, "getPrototypeOf", 1);
addFunction(_objectCtor, "getOwnPropertyDescriptor", 2);
addFunction(_objectCtor, "getOwnPropertyNames", newArray(), 1);
addFunction(_objectCtor, "create", 1);
addFunction(_objectCtor, "create", 1, 1);
addFunction(_objectCtor, "defineProperty", 3);
addFunction(_objectCtor, "defineProperties", 2);
addFunction(_objectCtor, "seal", 1);
......@@ -599,8 +610,8 @@ void ValueOwner::initializePrototypes()
_functionPrototype->setMember("constructor", _functionCtor);
addFunction(_functionPrototype, "toString", stringValue(), 0);
addFunction(_functionPrototype, "apply", 2);
addFunction(_functionPrototype, "call", 1);
addFunction(_functionPrototype, "bind", 1);
addFunction(_functionPrototype, "call", 1, 0, true);
addFunction(_functionPrototype, "bind", 1, 0, true);
// set up the default Array prototype
addFunction(_arrayCtor, "isArray", booleanValue(), 1);
......@@ -608,35 +619,35 @@ void ValueOwner::initializePrototypes()
_arrayPrototype->setMember("constructor", _arrayCtor);
addFunction(_arrayPrototype, "toString", stringValue(), 0);
addFunction(_arrayPrototype, "toLocalString", stringValue(), 0);
addFunction(_arrayPrototype, "concat", 0);
addFunction(_arrayPrototype, "concat", 0, 0, true);
addFunction(_arrayPrototype, "join", 1);
addFunction(_arrayPrototype, "pop", 0);
addFunction(_arrayPrototype, "push", 0);
addFunction(_arrayPrototype, "push", 0, 0, true);
addFunction(_arrayPrototype, "reverse", 0);
addFunction(_arrayPrototype, "shift", 0);
addFunction(_arrayPrototype, "slice", 2);
addFunction(_arrayPrototype, "sort", 1);
addFunction(_arrayPrototype, "splice", 2);
addFunction(_arrayPrototype, "unshift", 0);
addFunction(_arrayPrototype, "indexOf", numberValue(), 1);
addFunction(_arrayPrototype, "lastIndexOf", numberValue(), 1);
addFunction(_arrayPrototype, "every", 1);
addFunction(_arrayPrototype, "some", 1);
addFunction(_arrayPrototype, "forEach", 1);
addFunction(_arrayPrototype, "map", 1);
addFunction(_arrayPrototype, "filter", 1);
addFunction(_arrayPrototype, "reduce", 1);
addFunction(_arrayPrototype, "reduceRight", 1);
addFunction(_arrayPrototype, "unshift", 0, 0, true);
addFunction(_arrayPrototype, "indexOf", numberValue(), 2, 1);
addFunction(_arrayPrototype, "lastIndexOf", numberValue(), 2, 1);
addFunction(_arrayPrototype, "every", 2, 1);
addFunction(_arrayPrototype, "some", 2, 1);
addFunction(_arrayPrototype, "forEach", 2, 1);
addFunction(_arrayPrototype, "map", 2, 1);
addFunction(_arrayPrototype, "filter", 2, 1);
addFunction(_arrayPrototype, "reduce", 2, 1);
addFunction(_arrayPrototype, "reduceRight", 2, 1);
// set up the default String prototype
addFunction(_stringCtor, "fromCharCode", stringValue(), 0);
addFunction(_stringCtor, "fromCharCode", stringValue(), 0, 0, true);
_stringPrototype->setMember("constructor", _stringCtor);
addFunction(_stringPrototype, "toString", stringValue(), 0);
addFunction(_stringPrototype, "valueOf", stringValue(), 0);
addFunction(_stringPrototype, "charAt", stringValue(), 1);
addFunction(_stringPrototype, "charCodeAt", stringValue(), 1);
addFunction(_stringPrototype, "concat", stringValue(), 0);
addFunction(_stringPrototype, "concat", stringValue(), 0, 0, true);
addFunction(_stringPrototype, "indexOf", numberValue(), 2);
addFunction(_stringPrototype, "lastIndexOf", numberValue(), 2);
addFunction(_stringPrototype, "localeCompare", booleanValue(), 1);
......@@ -669,7 +680,7 @@ void ValueOwner::initializePrototypes()
addFunction(_numberCtor, "fromCharCode", 0);
_numberPrototype->setMember("constructor", _numberCtor);
addFunction(_numberPrototype, "toString", stringValue(), 0);
addFunction(_numberPrototype, "toString", stringValue(), 1, 1);
addFunction(_numberPrototype, "toLocaleString", stringValue(), 0);
addFunction(_numberPrototype, "valueOf", numberValue(), 0);
addFunction(_numberPrototype, "toFixed", numberValue(), 1);
......@@ -697,8 +708,8 @@ void ValueOwner::initializePrototypes()
addFunction(_mathObject, "exp", numberValue(), 1);
addFunction(_mathObject, "floor", numberValue(), 1);
addFunction(_mathObject, "log", numberValue(), 1);
addFunction(_mathObject, "max", numberValue(), 0);
addFunction(_mathObject, "min", numberValue(), 0);
addFunction(_mathObject, "max", numberValue(), 0, 0, true);
addFunction(_mathObject, "min", numberValue(), 0, 0, true);
addFunction(_mathObject, "pow", numberValue(), 2);
addFunction(_mathObject, "random", numberValue(), 1);
addFunction(_mathObject, "round", numberValue(), 1);
......@@ -708,6 +719,7 @@ void ValueOwner::initializePrototypes()
// set up the default Boolean prototype
addFunction(_dateCtor, "parse", numberValue(), 1);
addFunction(_dateCtor, "UTC", numberValue(), 7, 5);
addFunction(_dateCtor, "now", numberValue(), 0);
_datePrototype->setMember("constructor", _dateCtor);
......@@ -737,18 +749,18 @@ void ValueOwner::initializePrototypes()
addFunction(_datePrototype, "setTime", 1);
addFunction(_datePrototype, "setMilliseconds", 1);
addFunction(_datePrototype, "setUTCMilliseconds", 1);
addFunction(_datePrototype, "setSeconds", 1);
addFunction(_datePrototype, "setUTCSeconds", 1);
addFunction(_datePrototype, "setMinutes", 1);
addFunction(_datePrototype, "setUTCMinutes", 1);
addFunction(_datePrototype, "setHours", 1);
addFunction(_datePrototype, "setUTCHours", 1);
addFunction(_datePrototype, "setSeconds", 2, 1);
addFunction(_datePrototype, "setUTCSeconds", 2, 1);
addFunction(_datePrototype, "setMinutes", 3, 2);
addFunction(_datePrototype, "setUTCMinutes", 3, 2);
addFunction(_datePrototype, "setHours", 4, 3);
addFunction(_datePrototype, "setUTCHours", 4, 3);
addFunction(_datePrototype, "setDate", 1);
addFunction(_datePrototype, "setUTCDate", 1);
addFunction(_datePrototype, "setMonth", 1);
addFunction(_datePrototype, "setUTCMonth", 1);
addFunction(_datePrototype, "setFullYear", 1);
addFunction(_datePrototype, "setUTCFullYear", 1);
addFunction(_datePrototype, "setMonth", 2, 1);
addFunction(_datePrototype, "setUTCMonth", 2, 1);
addFunction(_datePrototype, "setFullYear", 3, 2);
addFunction(_datePrototype, "setUTCFullYear", 3, 2);
addFunction(_datePrototype, "toUTCString", stringValue(), 0);
addFunction(_datePrototype, "toISOString", stringValue(), 0);
addFunction(_datePrototype, "toJSON", stringValue(), 1);
......@@ -829,10 +841,12 @@ void ValueOwner::initializePrototypes()
f = addFunction(json, "parse", objectPrototype());
f->addArgument(stringValue(), "text");
f->addArgument(functionPrototype(), "reviver");
f->setOptionalNamedArgumentCount(1);
f = addFunction(json, "stringify", stringValue());
f->addArgument(unknownValue(), "value");
f->addArgument(unknownValue(), "replacer");
f->addArgument(unknownValue(), "space");
f->setOptionalNamedArgumentCount(2);
_globalObject->setMember("JSON", json);
// global Qt object, in alphabetic order
......
......@@ -142,8 +142,10 @@ public:
private:
void initializePrototypes();
Function *addFunction(ObjectValue *object, const QString &name, const Value *result, int argumentCount = 0);
Function *addFunction(ObjectValue *object, const QString &name, int argumentCount = 0);
Function *addFunction(ObjectValue *object, const QString &name, const Value *result,
int argumentCount = 0, int optionalCount = 0, bool variadic = false);
Function *addFunction(ObjectValue *object, const QString &name,
int argumentCount = 0, int optionalCount = 0, bool variadic = false);
private:
ObjectValue *_objectPrototype;
......
......@@ -41,6 +41,8 @@
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/functionhintproposal.h>
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
#include <texteditor/texteditorsettings.h>
#include <texteditor/completionsettings.h>
#include <utils/qtcassert.h>
......@@ -111,6 +113,13 @@ public:
virtual void operator()(const Value *base, const QString &name, const Value *value) = 0;
};
class CompleteFunctionCall
{
public:
CompleteFunctionCall(bool hasArguments = true) : hasArguments(hasArguments) {}
bool hasArguments;
};
class CompletionAdder : public PropertyProcessor
{
protected:
......@@ -127,8 +136,15 @@ public:
virtual void operator()(const Value *base, const QString &name, const Value *value)
{
Q_UNUSED(base)
Q_UNUSED(value)
addCompletion(completions, name, icon, order);
QVariant data;
if (const FunctionValue *func = value->asFunctionValue()) {
// constructors usually also have other interesting members,
// don't consider them pure functions and complete the '()'
if (!func->lookupMember("prototype", 0, 0, false)) {
data = QVariant::fromValue(CompleteFunctionCall(func->namedArgumentCount() || func->isVariadic()));
}
}
addCompletion(completions, name, icon, order, data);
}
QIcon icon;
......@@ -319,6 +335,8 @@ bool isLiteral(AST::Node *ast)
} // Anonymous
Q_DECLARE_METATYPE(CompleteFunctionCall)
// -----------------------
// QmlJSAssistProposalItem
// -----------------------
......@@ -338,12 +356,21 @@ void QmlJSAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor
editor->setCursorPosition(basePosition);
editor->remove(currentPosition - basePosition);
QString replaceable;
const QString &content = text();
if (content.endsWith(QLatin1String(": ")))
replaceable = QLatin1String(": ");
else if (content.endsWith(QLatin1Char('.')))
replaceable = QLatin1String(".");
QString content = text();
int cursorOffset = 0;
const CompletionSettings &completionSettings =
TextEditorSettings::instance()->completionSettings();
const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets;
if (autoInsertBrackets && data().canConvert<CompleteFunctionCall>()) {
CompleteFunctionCall function = data().value<CompleteFunctionCall>();
content += QLatin1String("()");
if (function.hasArguments)
cursorOffset = -1;
}
QString replaceable = content;
int replacedLength = 0;
for (int i = 0; i < replaceable.length(); ++i) {
const QChar a = replaceable.at(i);
......@@ -355,6 +382,8 @@ void QmlJSAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor
}
const int length = editor->position() - basePosition + replacedLength;
editor->replace(length, content);
if (cursorOffset)
editor->setCursorPosition(editor->position() + cursorOffset);
}
// -------------------------
......@@ -363,10 +392,12 @@ void QmlJSAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor
class FunctionHintProposalModel : public TextEditor::IFunctionHintProposalModel
{
public:
FunctionHintProposalModel(const QString &functionName, const QStringList &signature)
FunctionHintProposalModel(const QString &functionName, const QStringList &namedArguments,
int optionalNamedArguments, bool isVariadic)
: m_functionName(functionName)
, m_signature(signature)
, m_minimumArgumentCount(signature.size())
, m_namedArguments(namedArguments)
, m_optionalNamedArguments(optionalNamedArguments)
, m_isVariadic(isVariadic)
{}
virtual void reset() {}
......@@ -376,8 +407,9 @@ public:
private:
QString m_functionName;
QStringList m_signature;
int m_minimumArgumentCount;
QStringList m_namedArguments;
int m_optionalNamedArguments;
bool m_isVariadic;
};
QString FunctionHintProposalModel::text(int index) const
......@@ -388,11 +420,13 @@ QString FunctionHintProposalModel::text(int index) const
prettyMethod += QString::fromLatin1("function ");
prettyMethod += m_functionName;
prettyMethod += QLatin1Char('(');
for (int i = 0; i < m_minimumArgumentCount; ++i) {
for (int i = 0; i < m_namedArguments.size(); ++i) {
if (i == m_namedArguments.size() - m_optionalNamedArguments)
prettyMethod += QLatin1Char('[');
if (i != 0)
prettyMethod += QLatin1String(", ");
QString arg = m_signature.at(i);
QString arg = m_namedArguments.at(i);
if (arg.isEmpty()) {
arg = QLatin1String("arg");
arg += QString::number(i + 1);
......@@ -400,6 +434,13 @@ QString FunctionHintProposalModel::text(int index) const
prettyMethod += arg;
}
if (m_isVariadic) {
if (m_namedArguments.size())