Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-24009] Properly generate properties and methods from protocols #121

Merged
merged 6 commits into from
Mar 3, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions metabase/ios/Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ module.exports = function (grunt) {
ignoreLeaks: false,
globals: ['Hyperloop', 'HyperloopObject']
},
src: ['test/**/*_test.js'],
src: ['test/**/*_test.js']
},
jshint: {
options: {
jshintrc: true
jshintrc: true,
force: true
},
src: ['*.js','lib/**/*.js','test/**/*.js']
src: ['*.js', 'lib/**/*.js']
},
kahvesi: {
src: ['test/**/*.js']
Expand Down
91 changes: 82 additions & 9 deletions metabase/ios/lib/generate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,85 @@ function merge (src, dest) {
}
}

function superClassImplementsProxy (json, cls, proto) {
var prev;
while (cls && cls.superclass) {
prev = cls;
cls = cls.superclass;
if (cls) {
cls = json.classes[cls];
/**
* Checks if a parent class in the inheritance path already implemented
* the given protocol.
*
* @param {Object} json Native code metabase
* @param {Object} cls Class to traverse upwards from
* @param {String} proto The protocol to look for in parent classes
* @return {bool} True if protocol already implemented in a parent class, false otherwise.
*/
function isProtocolImplementedBySuperClass (json, cls, proto) {
var parentClass = cls && cls.superclass;
while (parentClass) {
if (parentClass.protocols && parentClass.protocols.indexOf(proto) !== -1) {
return true;
}
parentClass = parentClass.superclass ? json.classes[parentClass.superclass] : null;
}
return (prev && prev.protocols && prev.protocols.indexOf(proto) !== -1);

return false;
}

/**
* Iterates over all given protocols and processes protocol inheritance by
* incorporating one protocol into another through merging their methods and
* properties.
*
* @param {Object} protocols Object with protocols from the metabase
*/
function processProtocolInheritance (protocols) {
var mergedProtocols = [];
/**
* Recursively merges a protocol with all it's inherited protocols
*
* @param {Object} protocol A protocol
* @param {Number} logIntendationLevel Intendation level for debugging messages
*/
function mergeWithParentProtocols(protocol, logIntendationLevel) {
var logIntendationCharacter = " ";
var logIntendation = logIntendationCharacter.repeat(logIntendationLevel++);
var parentProtocols = protocol.protocols;
var protocolSignature = parentProtocols ? protocol.name + ' <' + parentProtocols.join(', ') + '>' : protocol.name;
util.logger.trace(logIntendation + 'Processing inherited protocols of ' + protocolSignature);
logIntendation = logIntendationCharacter.repeat(logIntendationLevel);

if (mergedProtocols.indexOf(protocol.name) !== -1) {
util.logger.trace(logIntendation + protocol.name + ' was already merged with all protocols it inherits from.');
return;
}
if (!parentProtocols) {
util.logger.trace(logIntendation + protocol.name + ' does not inherit from any other protocols.');
mergedProtocols.push(protocol.name);
return;
}

util.logger.trace(logIntendation + 'Iterating over inherited protocols of ' + protocol.name);
logIntendationLevel++;
protocol.protocols.forEach(function (parentProtocolName) {
if (protocol.name === parentProtocolName) {
util.logger.trace(logIntendation + 'Invalid protocol meta information. ' + protocol.name.red + ' cannot have itself as parent, skipping.');
return;
}
var parentProtocol = protocols[parentProtocolName];
mergeWithParentProtocols(parentProtocol, logIntendationLevel);

util.logger.trace(logIntendation + 'Merging ' + parentProtocol.name.cyan + ' => ' + protocol.name.cyan);
protocol.properties = protocol.properties || {};
protocol.methods = protocol.methods || {};
merge(parentProtocol.properties, protocol.properties);
merge(parentProtocol.methods, protocol.methods);
});

mergedProtocols.push(protocol.name);
}

Object.keys(protocols).forEach(function (protocolName) {
var protocol = protocols[protocolName];
var logIntendationLevel = 0;
mergeWithParentProtocols(protocol, logIntendationLevel);
});
}

function generateBuiltins (json, callback) {
Expand Down Expand Up @@ -146,6 +215,8 @@ function generateFromJSON (name, dir, json, state, callback, includes) {
}
}

processProtocolInheritance(json.protocols);

// classes
Object.keys(json.classes).forEach(function (k) {
var cls = json.classes[k];
Expand All @@ -155,11 +226,13 @@ function generateFromJSON (name, dir, json, state, callback, includes) {
// add protocols
if (cls.protocols && cls.protocols.length) {
cls.protocols.forEach(function (p) {
if (superClassImplementsProxy(json, cls, p)) {
if (isProtocolImplementedBySuperClass(json, cls, p)) {
return;
}
var protocol = json.protocols[p];
if (protocol) {
cls.properties = cls.properties || {};
cls.methods = cls.methods || {};
merge(protocol.properties, cls.properties);
merge(protocol.methods, cls.methods);
}
Expand Down
1 change: 1 addition & 0 deletions metabase/ios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"ejs": "^2.3.4",
"escodegen": "^1.7.0",
"plist": "^1.1.0",
"rewire": "^2.5.2",
"semver": "^5.0.3",
"walk-ast": "0.0.2",
"wrench": "^1.5.8"
Expand Down
12 changes: 12 additions & 0 deletions metabase/ios/test/fixtures/protocol_inheritance.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@protocol BaseProtocol <NSObject>

@property float a;
- (void)b;

@end

@protocol MyProtocol <BaseProtocol>

- (void)c;

@end
134 changes: 133 additions & 1 deletion metabase/ios/test/protocol_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
var should = require('should'),
var assert = require('assert'),
rewire = require('rewire'),
should = require('should'),
helper = require('./helper');

describe('protocol', function () {
Expand Down Expand Up @@ -196,4 +198,134 @@ describe('protocol', function () {
done();
});
});

it('should merge incorporated protocol methods and properties', function(done) {
helper.generate(helper.getFixture('protocol_inheritance.h'), helper.getTempFile('protocol.json'), function (err, json, sdk) {
assert.ifError(err);

var baseProtocol = {
filename: helper.getFixture('protocol_inheritance.h'),
framework: 'fixtures',
line: '1',
methods: {
a: {
arguments: [],
encoding: 'f16@0:8',
instance: true,
name: 'a',
returns: { encoding: 'f', type: 'float', value: 'float' },
selector: 'a'
},
b: {
arguments: [],
encoding: 'v16@0:8',
instance: true,
name: 'b',
returns: { encoding: 'v', type: 'void', value: 'void' },
selector: 'b'
},
'setA:': {
arguments: [
{ encoding: 'f', name: 'a', type: 'float', value: 'float' }
],
encoding: 'v20@0:8f16',
instance: true,
name: 'setA',
returns: { encoding: 'v', type: 'void', value: 'void' },
selector: 'setA:'
}
},
name: 'BaseProtocol',
properties: {
a: {
name: 'a',
optional: false,
type: { type: 'float', value: 'float' }
}
},
thirdparty: true
};
should(json).have.property('protocols', {
BaseProtocol: baseProtocol,
MyProtocol: {
filename: helper.getFixture('protocol_inheritance.h'),
framework: 'fixtures',
line: '8',
methods: {
c: {
arguments: [],
encoding: 'v16@0:8',
instance: true,
name: 'c',
returns: { encoding: 'v', type: 'void', value: 'void' },
selector: 'c'
}
},
name: 'MyProtocol',
protocols: [ 'BaseProtocol' ],
thirdparty: true
}
});

var generate = rewire('../lib/generate/index');
var processIncorporatedProtocols = generate.__get__('processIncorporatedProtocols');
processIncorporatedProtocols(json.protocols);
should(json).have.property('protocols', {
BaseProtocol: baseProtocol,
MyProtocol: {
filename: helper.getFixture('protocol_inheritance.h'),
framework: 'fixtures',
line: '8',
methods: {
a: {
arguments: [],
encoding: 'f16@0:8',
instance: true,
name: 'a',
returns: { encoding: 'f', type: 'float', value: 'float' },
selector: 'a'
},
b: {
arguments: [],
encoding: 'v16@0:8',
instance: true,
name: 'b',
returns: { encoding: 'v', type: 'void', value: 'void' },
selector: 'b'
},
c: {
arguments: [],
encoding: 'v16@0:8',
instance: true,
name: 'c',
returns: { encoding: 'v', type: 'void', value: 'void' },
selector: 'c'
},
'setA:': {
arguments: [
{ encoding: 'f', name: 'a', type: 'float', value: 'float' }
],
encoding: 'v20@0:8f16',
instance: true,
name: 'setA',
returns: { encoding: 'v', type: 'void', value: 'void' },
selector: 'setA:'
}
},
name: 'MyProtocol',
properties: {
a: {
name: 'a',
optional: false,
type: { type: 'float', value: 'float' }
}
},
protocols: [ 'BaseProtocol' ],
thirdparty: true
}
});

done();
});
});
});