Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions test-app/app/src/main/assets/app/mainpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ require("./tests/byte-buffer-test");
require("./tests/shared-array-buffer-test");
require("./tests/dex-interface-implementation");
require("./tests/testClassForNameDiscovery");
require("./tests/testPrebuiltExtendClasses");
require("./tests/testInterfaceImplementation");
require("./tests/testPrimitiveCallbackReturns");
require("./tests/testRuntimeImplementedAPIs");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exports.DollarFileObject = java.lang.Object.extend({
toString: function () {
return "dollar file";
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const LineOneObject = java.lang.Object.extend({ toString: function () { return "line one"; } });
107 changes: 107 additions & 0 deletions test-app/app/src/main/assets/app/tests/testPrebuiltExtendClasses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
describe("Prebuilt classes of anonymous extends", function () {

// DexFactory writes the classes it generates at runtime to one of these directories
// (see RuntimeHelper). A class the static binding generator built must be loaded from
// the APK and leave nothing here.
var filesDir = __dirname.replace(/\/app\/tests$/, "");
var dataDir = filesDir.replace(/\/files$/, "");
var dexDirs = [dataDir + "/code_cache/secondary-dexes", filesDir + "/secondary-dexes"];

function generatedDexFiles(instance) {
var proxyName = instance.getClass().getName().replace(/^com\.tns\.gen\./, "");
var result = [];
dexDirs.forEach(function (dir) {
var files = new java.io.File(dir).list();
if (!files) {
return;
}
for (var i = 0; i < files.length; i++) {
var file = files[i];
if (file.indexOf(proxyName + "-") === 0 && /\.dex$/.test(file)) {
result.push(file);
}
}
});
return result;
}

it("loads an anonymous extend's class from the APK", function () {
var Anonymous = java.lang.Object.extend({
toString: function () {
return "anonymous";
}
});
var instance = new Anonymous();

expect(instance.toString()).toBe("anonymous");
expect(generatedDexFiles(instance)).toEqual([]);
});

it("loads a TypeScript-compiled native class from the APK", function () {
var CompiledClass = (function (_super) {
__extends(CompiledClass, _super);
function CompiledClass() {
var _this = _super.call(this) || this;
return global.__native(_this);
}
CompiledClass.prototype.toString = function () {
return "compiled";
};
return CompiledClass;
}(java.lang.Object));
var instance = new CompiledClass();

expect(instance.toString()).toBe("compiled");
expect(generatedDexFiles(instance)).toEqual([]);
});

it("loads a TypeScript-compiled native class without a constructor from the APK", function () {
var NoConstructorClass = (function (_super) {
__extends(NoConstructorClass, _super);
function NoConstructorClass() {
return _super !== null && _super.apply(this, arguments) || this;
}
NoConstructorClass.prototype.toString = function () {
return "no constructor";
};
return NoConstructorClass;
}(java.lang.Object));
var instance = new NoConstructorClass();

expect(instance.toString()).toBe("no constructor");
expect(generatedDexFiles(instance)).toEqual([]);
});

it("loads the class of an extend in a file with $ in its name from the APK", function () {
var DollarFileObject = require("./prebuiltExtend$Dollar").DollarFileObject;
var instance = new DollarFileObject();

expect(instance.toString()).toBe("dollar file");
expect(generatedDexFiles(instance)).toEqual([]);
});

it("loads the class of an extend on the first line of an ES module from the APK", function () {
var LineOneObject = require("./prebuiltExtendLineOne.mjs").LineOneObject;
var instance = new LineOneObject();

expect(instance.toString()).toBe("line one");
expect(generatedDexFiles(instance)).toEqual([]);
});

it("caches a class generated at runtime under the name DexFactory looks up", function () {
// A computed member hides the extend from the static binding generator.
var ext = "ex" + "tend";
var RuntimeGenerated = java.lang.Object[ext]({
toString: function () {
return "runtime generated";
}
});
var instance = new RuntimeGenerated();

expect(instance.toString()).toBe("runtime generated");
var files = generatedDexFiles(instance);
expect(files.length).toBe(1);
// <name>-<update time>-<version code>-<content digest>.dex, as DexFactory.getDexFile probes it.
expect(files[0]).toMatch(/-\d+-\d+-[0-9a-f]{10}\.dex$/);
});
});
5 changes: 3 additions & 2 deletions test-app/app/src/main/assets/app/tests/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1625,8 +1625,9 @@ describe("Tests ", function () {
__log("TEST: TestCanParseSignatureWithTypesThatContainsCapitalLettersForPrimitiveTypes");

var formats = java.lang.reflect.Array.newInstance(java.text.NumberFormat.class, 2);
formats[0] = java.text.NumberFormat.getInstance();
formats[1] = java.text.NumberFormat.getIntegerInstance();
// The pattern below uses "," as a separator, which is a decimal separator in many locales.
formats[0] = java.text.NumberFormat.getInstance(java.util.Locale.US);
formats[1] = java.text.NumberFormat.getIntegerInstance(java.util.Locale.US);
var mf = new java.text.MessageFormat("{0}, {1}")
mf.setFormats(formats);
var arr = mf.parse("123, 4567");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var NoConstructor = /** @class */ (function (_super) {
__extends(NoConstructor, _super);
function NoConstructor() {
return _super !== null && _super.apply(this, arguments) || this;
}
NoConstructor.prototype.toString = function () {
return "no constructor";
};
return NoConstructor;
}(java.lang.Object));
var Minified=function(e){__extends(Minified,e);function Minified(){return null!==e&&e.apply(this,arguments)||this}Minified.prototype.toString=function(){return"minified"};return Minified}(java.lang.Object);
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
(function () {

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length;
var r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;

if (typeof global.Reflect === "object" && typeof global.Reflect.decorate === "function") {
r = global.Reflect.decorate(decorators, target, key, desc);
}
else {
for (var i = decorators.length - 1; i >= 0; i--) {
if (d = decorators[i]) {
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
}
}
}
return c > 3 && r && Object.defineProperty(target, key, r), r;
};

// For backward compatibility.
var __native = function (thiz) {
// we are setting the __container__ property to the base class when the super method is called
// if the constructor returns the __native(this) call we will use the old implementation
// copying all the properties to the result
// otherwise if we are using the result from the super() method call we won't need such logic
// as thiz already contains the parent properties
// this way we now support both implementations in typescript generated constructors:
// 1: super(); return __native(this);
// 2: return super() || this;
if (thiz.__container__) {
var result = thiz.__proto__;

for (var prop in thiz) {
if (thiz.hasOwnProperty(prop)) {
thiz.__proto__[prop] = thiz[prop];
delete thiz[prop];
}
}

thiz.constructor = undefined;
thiz.__proto__ = undefined;
Object.freeze(thiz);
Object.preventExtensions(thiz);
return result;
} else {
return thiz;
}
};

var __extends = function (Child, Parent) {
var extendNativeClass = !!Parent.extend && (Parent.extend.toString().indexOf("[native code]") > -1);
if (!extendNativeClass) {
__extends_ts(Child, Parent);
return;
}
if (Parent.__isPrototypeImplementationObject) {
throw new Error("Can not extend an already extended native object.");
}

function extend(thiz) {
var child = thiz.__proto__.__child;
if (!child.__extended) {
var parent = thiz.__proto__.__parent;
child.__extended = parent.extend(child.name, child.prototype, true);
// This will deal with "i instanceof child"
child[Symbol.hasInstance] = function (instance) {
return instance instanceof this.__extended;
}
}
return child.__extended;
};

Parent.__activityExtend = function (parent, name, implementationObject) {
__log("__activityExtend called");
return parent.extend(name, implementationObject);
};

Parent.call = function (thiz) {
var Extended = extend(thiz);
thiz.__container__ = true;
if (arguments.length > 1) {
thiz.__proto__ = new (Function.prototype.bind.apply(Extended, [null].concat(Array.prototype.slice.call(arguments, 1))));
}
else {
thiz.__proto__ = new Extended()
}
return thiz.__proto__;
};

Parent.apply = function (thiz, args) {
var Extended = extend(thiz);
thiz.__container__ = true;
if (args && args.length > 0) {
thiz.__proto__ = new (Function.prototype.bind.apply(Extended, [null].concat(args)));
}
else {
thiz.__proto__ = new Extended();
}
return thiz.__proto__;
};
__extends_ns(Child, Parent);
Child.__isPrototypeImplementationObject = true;
Child.__proto__ = Parent;
Child.prototype.__parent = Parent;
Child.prototype.__child = Child;
}

var __extends_ts = function (child, parent) {
extendStaticFunctions(child, parent);
assignPrototypeFromParentToChild(parent, child);
};

var __extends_ns = function (child, parent) {
if (!parent.extend) {
assignPropertiesFromParentToChild(parent, child);
}

assignPrototypeFromParentToChild(parent, child);
};

var extendStaticFunctions =
Object.setPrototypeOf
|| (hasInternalProtoProperty() && function (child, parent) { child.__proto__ = parent; })
|| assignPropertiesFromParentToChild;

function hasInternalProtoProperty() {
return { __proto__: [] } instanceof Array;
}

function assignPropertiesFromParentToChild(parent, child) {
for (var property in parent) {
if (parent.hasOwnProperty(property)) {
child[property] = parent[property];
}
}
}

function assignPrototypeFromParentToChild(parent, child) {
function __() {
this.constructor = child;
}

if (parent === null) {
child.prototype = Object.create(null);
} else {
__.prototype = parent.prototype;
child.prototype = new __();
}
}


function JavaProxy(className) {
return function (target) {
var extended = target.extend(className, target.prototype)
extended.name = className;
return extended;
};
}

function Interfaces(interfacesArr) {
return function (target) {
if (interfacesArr instanceof Array) {
// attach interfaces: [] to the object
target.prototype.interfaces = interfacesArr;
}
}
}

Object.defineProperty(global, "__native", { value: __native });
Object.defineProperty(global, "__extends", { value: __extends });
Object.defineProperty(global, "__decorate", { value: __decorate });

global.JavaProxy = JavaProxy;
global.Interfaces = Interfaces;
})()
28 changes: 28 additions & 0 deletions test-app/build-tools/jsparser/tests/specs/ast-parser-tests.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,34 @@ describe("parser/js_parser tests", function () {
});
});

it("Generated metadata for a transpiled typescript class without a constructor should contain the super call location", function (done) {
let input = path.normalize(path.join(prefix, "extends_ts_no_constructor", "app")),
generatedJavaClassesRoot = path.normalize(path.join(prefix, "extends_ts_no_constructor", "src", "main", "java"));

clearOutput();

execGradle(input, generatedJavaClassesRoot, function (error, stdout, stderr) {
if (error) {
console.error(`exec error: ${error}`);
return done.fail(error);
}

logExecResult(stdout, stderr)

let bindingsContent = fs.readFileSync(sbgBindingOutoutFile, "utf-8").toString().trim().split('\n');

// JavaClass*file*line*column*NewClass*...: the runtime names the class from
// the location of the `_super.apply` call (the column of `apply`, 1-based).
let locations = bindingsContent.map(line => line.split("*").slice(1, 5).join("*")).sort();
expect(locations).toEqual([
"no_constructor*11*87*Minified",
"no_constructor*4*42*NoConstructor"
]);

done();
});
});

it("Generated metadata for bindings should return proper JavaClass name when there is a static property and double assignment", function (done) {
let input = path.normalize(path.join(prefix, "decorated_double_assignment_ts", "app")),
generatedJavaClassesRoot = path.normalize(path.join(prefix, "decorated_double_assignment_ts", "src", "main", "java"));
Expand Down
Loading
Loading