blob: 05bb03453ea59a6f73ea98d0a6c0075855eaea9b [file] [log] [blame]
/*
* Copyright (C) 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WI.PropertyPath = class PropertyPath
{
constructor(object, pathComponent, parent, isPrototype)
{
console.assert(object instanceof WI.RemoteObject || object === null);
console.assert(!pathComponent || typeof pathComponent === "string");
console.assert(!parent || parent instanceof WI.PropertyPath);
console.assert(!parent || pathComponent.length);
// We allow property pathes with null objects as end-caps only.
// Disallow appending to a PropertyPath with null objects.
if (parent && !parent.object)
throw new Error("Attempted to append to a PropertyPath with null object.");
this._object = object;
this._pathComponent = typeof pathComponent === "string" ? pathComponent : null;
this._parent = parent || null;
this._isPrototype = isPrototype || false;
}
// Static
static emptyPropertyPathForScope(object)
{
return new WI.PropertyPath(object, WI.PropertyPath.SpecialPathComponent.EmptyPathComponentForScope);
}
// Public
get object() { return this._object; }
get parent() { return this._parent; }
get isPrototype() { return this._isPrototype; }
get pathComponent() { return this._pathComponent; }
get rootObject()
{
return this._parent ? this._parent.rootObject : this._object;
}
get lastNonPrototypeObject()
{
if (!this._parent)
return this._object;
var p = this._parent;
while (p) {
if (!p.isPrototype)
break;
if (!p.parent)
break;
p = p.parent;
}
return p.object;
}
get fullPath()
{
var components = [];
for (var p = this; p && p.pathComponent; p = p.parent)
components.push(p.pathComponent);
components.reverse();
return components.join("");
}
get reducedPath()
{
// The display path for a value should not include __proto__.
// The path for "foo.__proto__.bar.__proto__.x" is better shown as "foo.bar.x".
// FIXME: We should keep __proto__ if this property was overridden.
var components = [];
var p = this;
// Include trailing __proto__s.
for (; p && p.isPrototype; p = p.parent)
components.push(p.pathComponent);
// Skip other __proto__s.
for (; p && p.pathComponent; p = p.parent) {
if (p.isPrototype)
continue;
components.push(p.pathComponent);
}
components.reverse();
return components.join("");
}
displayPath(type)
{
return type === WI.PropertyPath.Type.Value ? this.reducedPath : this.fullPath;
}
isRoot()
{
return !this._parent;
}
isScope()
{
return this._pathComponent === WI.PropertyPath.SpecialPathComponent.EmptyPathComponentForScope;
}
isPathComponentImpossible()
{
return this._pathComponent && this._pathComponent.startsWith("@");
}
isFullPathImpossible()
{
if (this.isPathComponentImpossible())
return true;
if (this._parent)
return this._parent.isFullPathImpossible();
return false;
}
appendPropertyName(object, propertyName)
{
var isPrototype = propertyName === "__proto__";
if (this.isScope())
return new WI.PropertyPath(object, propertyName, this, isPrototype);
var component = this._canPropertyNameBeDotAccess(propertyName) ? "." + propertyName : "[" + doubleQuotedString(propertyName) + "]";
return new WI.PropertyPath(object, component, this, isPrototype);
}
appendPropertySymbol(object, symbolName)
{
var component = WI.PropertyPath.SpecialPathComponent.SymbolPropertyName + (symbolName.length ? "(" + symbolName + ")" : "");
return new WI.PropertyPath(object, component, this);
}
appendInternalPropertyName(object, propertyName)
{
var component = WI.PropertyPath.SpecialPathComponent.InternalPropertyName + "[" + propertyName + "]";
return new WI.PropertyPath(object, component, this);
}
appendGetterPropertyName(object, propertyName)
{
var component = ".__lookupGetter__(" + doubleQuotedString(propertyName) + ")";
return new WI.PropertyPath(object, component, this);
}
appendSetterPropertyName(object, propertyName)
{
var component = ".__lookupSetter__(" + doubleQuotedString(propertyName) + ")";
return new WI.PropertyPath(object, component, this);
}
appendArrayIndex(object, indexString)
{
var component = "[" + indexString + "]";
return new WI.PropertyPath(object, component, this);
}
appendMapKey(object)
{
var component = WI.PropertyPath.SpecialPathComponent.MapKey;
return new WI.PropertyPath(object, component, this);
}
appendMapValue(object, keyObject)
{
console.assert(!keyObject || keyObject instanceof WI.RemoteObject);
if (keyObject && keyObject.hasValue()) {
if (keyObject.type === "string") {
var component = ".get(" + doubleQuotedString(keyObject.description) + ")";
return new WI.PropertyPath(object, component, this);
}
var component = ".get(" + keyObject.description + ")";
return new WI.PropertyPath(object, component, this);
}
var component = WI.PropertyPath.SpecialPathComponent.MapValue;
return new WI.PropertyPath(object, component, this);
}
appendSetIndex(object)
{
var component = WI.PropertyPath.SpecialPathComponent.SetIndex;
return new WI.PropertyPath(object, component, this);
}
appendSymbolProperty(object)
{
var component = WI.PropertyPath.SpecialPathComponent.SymbolPropertyName;
return new WI.PropertyPath(object, component, this);
}
appendPropertyDescriptor(object, descriptor, type)
{
console.assert(descriptor instanceof WI.PropertyDescriptor);
if (descriptor.isInternalProperty)
return this.appendInternalPropertyName(object, descriptor.name);
if (descriptor.symbol)
return this.appendSymbolProperty(object);
if (type === WI.PropertyPath.Type.Getter)
return this.appendGetterPropertyName(object, descriptor.name);
if (type === WI.PropertyPath.Type.Setter)
return this.appendSetterPropertyName(object, descriptor.name);
console.assert(type === WI.PropertyPath.Type.Value);
if (this._object.subtype === "array" && !isNaN(parseInt(descriptor.name)))
return this.appendArrayIndex(object, descriptor.name);
return this.appendPropertyName(object, descriptor.name);
}
// Private
_canPropertyNameBeDotAccess(propertyName)
{
return /^(?![0-9])\w+$/.test(propertyName);
}
};
WI.PropertyPath.SpecialPathComponent = {
InternalPropertyName: "@internal",
SymbolPropertyName: "@symbol",
MapKey: "@mapkey",
MapValue: "@mapvalue",
SetIndex: "@setindex",
EmptyPathComponentForScope: "",
};
WI.PropertyPath.Type = {
Value: "value",
Getter: "getter",
Setter: "setter",
};