Skip to content
This repository has been archived by the owner on Mar 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #76 from calimarkus/codingLegacyKey
Browse files Browse the repository at this point in the history
[RMCoding] Add support for %codingLegacyKey annotation
  • Loading branch information
ColinCampbell committed Feb 5, 2018
2 parents 50ceb76 + c2bc7a4 commit 25d04d6
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 43 deletions.
79 changes: 42 additions & 37 deletions features/coding.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Feature: Outputting Value Objects With Coded Values

@announce
Scenario: Generating Header Files including Coding
Scenario: Generating correct NSCoding headers & implementation with 4 different types
Given a file named "project/values/RMPage.value" with:
"""
RMPage includes(RMCoding) {
Expand Down Expand Up @@ -38,13 +38,6 @@ Feature: Outputting Value Objects With Coded Values
"""
And the file "project/values/RMPage.m" should contain:
"""
#import "RMPage.h"
static __unsafe_unretained NSString * const kDoesUserLikeKey = @"DOES_USER_LIKE";
static __unsafe_unretained NSString * const kIdentifierKey = @"IDENTIFIER";
static __unsafe_unretained NSString * const kLikeCountKey = @"LIKE_COUNT";
static __unsafe_unretained NSString * const kNumberOfRatingsKey = @"NUMBER_OF_RATINGS";
@implementation RMPage
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
Expand Down Expand Up @@ -87,38 +80,50 @@ Feature: Outputting Value Objects With Coded Values
[aCoder encodeInteger:_likeCount forKey:kLikeCountKey];
[aCoder encodeInteger:_numberOfRatings forKey:kNumberOfRatingsKey];
}
"""

- (NSUInteger)hash
{
NSUInteger subhashes[] = {(NSUInteger)_doesUserLike, [_identifier hash], ABS(_likeCount), _numberOfRatings};
NSUInteger result = subhashes[0];
for (int ii = 1; ii < 4; ++ii) {
unsigned long long base = (((unsigned long long)result) << 32 | subhashes[ii]);
base = (~base) + (base << 18);
base ^= (base >> 31);
base *= 21;
base ^= (base >> 11);
base += (base << 6);
base ^= (base >> 22);
result = base;
}
return result;
@announce
Scenario: Generating correct NSCoding implementation with specified legacyKeys
Given a file named "project/values/RMPage.value" with:
"""
RMPage includes(RMCoding) {
%codingLegacyKey name=old_does_user_like
BOOL doesUserLike
%codingLegacyKey name=old_identifier
NSString* identifier
%codingLegacyKey name=old_like_count
NSInteger likeCount
%codingLegacyKey name=old_number_of_ratings
NSUInteger numberOfRatings
}
- (BOOL)isEqual:(RMPage *)object
"""
And a file named "project/.valueObjectConfig" with:
"""
{ }
"""
When I run `../../bin/generate project`
And the file "project/values/RMPage.m" should contain:
"""
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self == object) {
return YES;
} else if (self == nil || object == nil || ![object isKindOfClass:[self class]]) {
return NO;
if ((self = [super init])) {
_doesUserLike = [aDecoder decodeBoolForKey:kDoesUserLikeKey];
if (_doesUserLike == NO) {
_doesUserLike = [aDecoder decodeBoolForKey:@"old_does_user_like"];
}
_identifier = [aDecoder decodeObjectForKey:kIdentifierKey];
if (_identifier == nil) {
_identifier = [aDecoder decodeObjectForKey:@"old_identifier"];
}
_likeCount = [aDecoder decodeIntegerForKey:kLikeCountKey];
if (_likeCount == 0) {
_likeCount = [aDecoder decodeIntegerForKey:@"old_like_count"];
}
_numberOfRatings = [aDecoder decodeIntegerForKey:kNumberOfRatingsKey];
if (_numberOfRatings == 0) {
_numberOfRatings = [aDecoder decodeIntegerForKey:@"old_number_of_ratings"];
}
}
return
_doesUserLike == object->_doesUserLike &&
_likeCount == object->_likeCount &&
_numberOfRatings == object->_numberOfRatings &&
(_identifier == object->_identifier ? YES : [_identifier isEqual:object->_identifier]);
return self;
}
@end
"""
145 changes: 144 additions & 1 deletion src/__tests__/plugins/coding-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,43 @@ describe('ObjectSpecPlugins.Coding', function() {
];
expect(errors).toEqualJSON(expectedErrors);
});

it('returns a validation error when there is an attribute with legacy key with unsupported type', function() {
const objectType:ObjectSpec.Type = {
annotations: {},
attributes: [
{
annotations: {
codingLegacyKey: [{
properties: {name: 'legacySizeCodingKey'}
}]
},
comments: [],
name: 'size',
nullability:ObjC.Nullability.Inherited(),
type: {
fileTypeIsDefinedIn:Maybe.Nothing<string>(),
libraryTypeIsDefinedIn:Maybe.Nothing<string>(),
name:'CGSize',
reference: 'CGSize',
underlyingType:Maybe.Nothing<string>(),
conformingProtocol: Maybe.Nothing<string>()
}
}
],
comments: [],
typeLookups:[],
excludes: [],
includes: [],
libraryName: Maybe.Nothing<string>(),
typeName: 'Foo'
};
const errors:Error.Error[] = ObjectSpecPlugin.validationErrors(objectType);
const expectedErrors:Error.Error[] = [
Error.Error('%codingLegacyKey can\'t be used with "CGSize" at Foo.size.')
];
expect(errors).toEqualJSON(expectedErrors);
});
});

describe('#instanceMethods', function() {
Expand All @@ -182,7 +219,102 @@ describe('ObjectSpecPlugins.Coding', function() {
expect(instanceMethods).toEqualJSON([]);
});

it('returns two instance methods which will encode and decode two values when called', function() {
it('returns two instance methods which will encode and decode a value, respecting the legacy key name', function() {
const objectType:ObjectSpec.Type = {
annotations: {},
attributes: [
{
annotations: {
codingLegacyKey: [{
properties: {name: 'oldNameKey'}
}]
},
comments: [],
name: 'name',
nullability:ObjC.Nullability.Inherited(),
type: {
fileTypeIsDefinedIn:Maybe.Nothing<string>(),
libraryTypeIsDefinedIn:Maybe.Nothing<string>(),
name: 'NSString',
reference: 'NSString *',
underlyingType:Maybe.Just<string>('NSObject'),
conformingProtocol: Maybe.Nothing<string>()
}
}
],
comments: [],
typeLookups:[],
excludes: [],
includes: [],
libraryName: Maybe.Nothing<string>(),
typeName: 'Foo'
};

const instanceMethods:ObjC.Method[] = ObjectSpecPlugin.instanceMethods(objectType);

const expectedInstanceMethods:ObjC.Method[] = [
{
belongsToProtocol:Maybe.Just<string>('NSCoding'),
code: [
'if ((self = [super init])) {',
' _name = [aDecoder decodeObjectForKey:kNameKey];',
' if (_name == nil) {',
' _name = [aDecoder decodeObjectForKey:@"oldNameKey"];',
' }',
'}',
'return self;'
],
comments: [],
compilerAttributes:[],
keywords: [
{
name: 'initWithCoder',
argument: Maybe.Just<ObjC.KeywordArgument>({
name: 'aDecoder',
modifiers: [],
type: {
name: 'NSCoder',
reference: 'NSCoder *'
}
})
}
],
returnType: {
type:Maybe.Just<ObjC.Type>({
name: 'instancetype',
reference: 'instancetype'
}),
modifiers:[ObjC.KeywordArgumentModifier.Nullable()]
}
},
{
belongsToProtocol:Maybe.Just<string>('NSCoding'),
code: [
'[aCoder encodeObject:_name forKey:kNameKey];',
],
comments: [],
compilerAttributes:[],
keywords: [
{
name: 'encodeWithCoder',
argument: Maybe.Just<ObjC.KeywordArgument>({
name: 'aCoder',
modifiers: [],
type: {
name: 'NSCoder',
reference: 'NSCoder *'
}
})
}
],
returnType:{ type:Maybe.Nothing<ObjC.Type>(), modifiers:[] },
}
];

expect(instanceMethods).toEqualJSON(expectedInstanceMethods);
});

it('returns two instance methods which will encode and decode three values when called', function() {
const objectType:ObjectSpec.Type = {
annotations: {},
attributes: [
Expand Down Expand Up @@ -523,6 +655,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'doesUserLike',
valueAccessor: '_doesUserLike',
constantName: 'kDoesUserLikeKey',
legacyKeyName: '',
type: {
name: 'BOOL',
reference: 'BOOL'
Expand All @@ -539,6 +672,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'foo',
valueAccessor: '_foo',
constantName: 'kFooKey',
legacyKeyName: '',
type: {
name: 'id',
reference: 'id'
Expand All @@ -555,6 +689,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'name',
valueAccessor: '_name',
constantName: 'kNameKey',
legacyKeyName: '',
type: {
name: 'NSObject',
reference: 'NSObject *'
Expand All @@ -571,6 +706,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'age',
valueAccessor: '_age',
constantName: 'kAgeKey',
legacyKeyName: '',
type: {
name: 'NSInteger',
reference: 'NSInteger'
Expand All @@ -587,6 +723,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'age',
valueAccessor: '_age',
constantName: 'kAgeKey',
legacyKeyName: '',
type: {
name: 'NSUInteger',
reference: 'NSUInteger'
Expand All @@ -603,6 +740,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'callbackMethod',
valueAccessor: '_callbackMethod',
constantName: 'kCallbackMethodKey',
legacyKeyName: '',
type: {
name: 'SEL',
reference: 'SEL'
Expand All @@ -621,6 +759,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'doesUserLike',
valueAccessor: '_doesUserLike',
constantName: 'kDoesUserLikeKey',
legacyKeyName: '',
type: {
name: 'BOOL',
reference: 'BOOL'
Expand All @@ -637,6 +776,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'name',
valueAccessor: '_name',
constantName: 'kNameKey',
legacyKeyName: '',
type: {
name: 'NSObject',
reference: 'NSObject *'
Expand All @@ -653,6 +793,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'age',
valueAccessor: '_age',
constantName: 'kAgeKey',
legacyKeyName: '',
type: {
name: 'NSInteger',
reference: 'NSInteger'
Expand All @@ -669,6 +810,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'age',
valueAccessor: '_age',
constantName: 'kAgeKey',
legacyKeyName: '',
type: {
name: 'NSUInteger',
reference: 'NSUInteger'
Expand All @@ -685,6 +827,7 @@ describe('ObjectSpecPlugins.Coding', function() {
name: 'callbackMethod',
valueAccessor: '_callbackMethod',
constantName: 'kCallbackMethodKey',
legacyKeyName: '',
type: {
name: 'SEL',
reference: 'SEL'
Expand Down

0 comments on commit 25d04d6

Please sign in to comment.