Issues (204)

src/InterfaceBuilder.js (2 issues)

1
/* Javascript Object Inheritance Implementation                ______  ________
2
 * (c) 2016 <[email protected]>                             __ / / __ \/  _/  _/
3
 * Licensed under MIT.                                    / // / /_/ // /_/ /
4
 * ------------------------------------------------------ \___/\____/___/__*/
5
6
JOII = typeof (JOII) !== 'undefined' ? JOII : {};
7
JOII.InterfaceRegistry = {};
8
9
/**
10
 * Builds an interface for a class to enforce implementation and signature
11
 * of a set of properties and methods.
12
 *
13
 * @return {Object}
14
 */
15
JOII.InterfaceBuilder = function() {
16
17
    var args        = JOII.Compat.ParseArguments(arguments),
0 ignored issues
show
This function should enable strict mode with use strict.

Strict mode is a way to opt-in to a restricted variant of JavaScript. It eliminates some common pitfalls by being less lenient and raising more errors.

Besides, it is also used to fix certain mistakes which made it difficult for JavaScript runtimes to perform certain optimizations.

We generally recommend to only enable strict mode on the function scope and not in the global scope as that might break third-party scripts on your website.

Loading history...
18
        name        = args.name,
19
        parameters  = args.parameters,
20
        body        = args.body;
21
22
    // Start by creating a prototype based on the parameters and body.
23
    // The definition will be the resulting function containing all
24
    // required information about this interface.
25
    var prototype  = JOII.PrototypeBuilder(name, parameters, body, true),
26
        definition = function(prototype) {
27
            var reflector = new JOII.Reflection.Class(prototype),
28
                properties = this.reflector.getProperties(),
29
                methods = this.reflector.getMethods(),
30
                i, p1, p2;
31
32
            // If the class is marked as 'abstract', running interface validation
33
            // on it is rather useless since the class can't be instantiated.
34
            if (reflector.isAbstract()) {
35
                return true;
36
            }
37
38
            var verifyMeta = function(t, p1, p2, prefix) {
39
                if (p1.getVisibility() !== p2.getVisibility()) {
40
                    throw prefix + ' ' + p2.getName() + ' cannot be ' + p2.getVisibility() + ' because the interface declared it ' + p1.getVisibility() + '.';
41
                }
42
                if (prefix != 'Method') {
43
                    if (p1.getType() !== p2.getType()) {
44
                        throw prefix + ' ' + p2.getName() + ' cannot be declared as ' + p2.getType() + ' because the interface declared it as ' + p1.getType() + '.';
45
                    }
46
                    if (p1.isNullable() !== p2.isNullable()) {
47
                        throw prefix + ' ' + p2.getName() + ' must be nullable as defined in the interface ' + t.name + '.';
48
                    }
49
                }
50
                return true;
51
            };
52
53
54
            // Verify that all properties exist and have the correct metadata.
55
            for (i in properties) {
56
                if (properties.hasOwnProperty(i) === false) continue;
57
                p1 = properties[i];
58
                
59
                if (p1.isStatic() && !reflector.isStatic()) continue;
60
                if (!p1.isStatic() && reflector.isStatic()) continue;
61
62
63
                if (!reflector.hasProperty(p1.getName())) {
64
                    throw 'Class must implement ' + (p1.toString().split(':')[0]) + ' as defined in the interface ' + this.name + '.';
65
                }
66
                p2 = reflector.getProperty(p1.getName());
67
68
                // Verify meta data
69
                verifyMeta(this, p1, p2, 'Property');
70
            }
71
72
            // Verify methods.
73
            for (i in methods) {
74
                if (methods.hasOwnProperty(i) === false) continue;
75
                p1 = methods[i];
76
77
                if (p1.isStatic() && !reflector.isStatic()) continue;
78
                if (!p1.isStatic() && reflector.isStatic()) continue;
79
80
                if (!reflector.hasMethod(p1.getName())) {
81
                    throw 'Class must implement ' + (p1.toString().split(':')[0]) + ' as defined in the interface ' + this.name + '.';
82
                }
83
                p2 = reflector.getMethod(p1.getName());
84
85
                // Verify meta data
86
                verifyMeta(this, p1, p2, 'Method');
87
88
                // Verify function signature.
89
                var args_interface = p1.getParameters();
90
                var args_class = p2.getParameters();
91
92
                if (args_interface.length == 0 || typeof (args_interface[0]) !== 'object') {
0 ignored issues
show
It is recommended to use === to compare with 0.

Generally, it is recommended to use strict comparison whenever possible and not to rely on the weaker type-juggling comparison operator.

Read more about comparison operations.

Loading history...
93
                    // fallback for backwards compatibility
94
                    if (args_interface.length !== args_class.length) {
95
                        throw 'Method ' + p1.getName() + ' does not match the parameter count as defined in the interface ' + this.name + '.';
96
                    }
97
                } else {
98
                    for (var idx = 0; idx < args_interface.length; idx++) {
99
                        var interface_parameters_meta = args_interface[idx];
100
101
                        var different = true;
102
103
                        for (var x = 0; x < args_class.length; x++) {
104
                            var class_parameters_meta = args_class[x];
105
106
                            if (interface_parameters_meta.length === class_parameters_meta.length) {
107
                                // this signature has the same number of types as the new signature
108
                                // check to see if the types are the same (duplicate signature)
109
                                different = false;
110
111
                                for (var y = 0; y < interface_parameters_meta.length; y++) {
112
                                    if (interface_parameters_meta[y].type != class_parameters_meta[y].type) {
113
                                        different = true;
114
                                    }
115
                                }
116
                                if (!different) {
117
                                    break;
118
                                }
119
                            }
120
                        }
121
                        if (different) {
122
                            throw 'Method ' + p1.getName() + ' does not match the parameter types as defined in the interface ' + this.name + '.';
123
                        }
124
                    }
125
                }
126
            }
127
        };
128
129
    // Set our interface specification
130
    JOII.CreateProperty(definition, '__interface__', {
131
        prototype : prototype,
132
        reflector : new JOII.Reflection.Class(prototype),
133
        name      : name
134
    });
135
136
    // And the standard JOII-metadata.
137
    JOII.CreateProperty(definition, '__joii__', prototype.__joii__);
138
139
    var constructor = JOII.Compat.Bind(definition, definition.__interface__);
140
    constructor.prototype = prototype;
141
142
    // Properties and methods may ever be declared as abstract or final in
143
    // an interface definition, because that wouldn't make any sense in
144
    // this context.
145
    var properties = definition.__interface__.reflector.getProperties(),
146
        methods    = definition.__interface__.reflector.getMethods(),
147
        validate   = function(prop, prefix) {
148
        if (prop.isAbstract()) {
149
            throw 'An interface may not contain abstract definitions. ' + prefix + ' ' + prop.getName() + ' is abstract in interface ' + definition.__interface__.name + '.';
150
        }
151
        if (prop.isFinal()) {
152
            throw 'An interface may not contain final definitions. ' + prefix + ' ' + prop.getName() + ' is final in interface ' + definition.__interface__.name + '.';
153
        }
154
    };
155
156
    // Validate properties and methods.
157
    var i;
158
    for (i in properties) {
159
        if (properties.hasOwnProperty(i) === false) continue;
160
        validate(properties[i], 'Property');
161
    }
162
    for (i in methods) {
163
        if (methods.hasOwnProperty(i) === false) continue;
164
        validate(methods[i], 'Method');
165
    }
166
167
    // Apply the definition to the constructor to have access to metadata
168
    // without running or instantiating the function.
169
    JOII.CreateProperty(constructor, 'definition', definition);
170
171
    // Register the interface, making it available in the PrototypeBuilder
172
    // to use as a type in property definitions.
173
    if (typeof (JOII.InterfaceRegistry[name]) !== 'undefined') {
174
        throw 'Another interface with the name "' + name + '" already exists.';
175
    }
176
    if (JOII.Compat.indexOf(JOII.InternalTypeNames, name) !== -1) {
177
        throw 'An interface may not be named "' + name + '", becase that name is reserved.';
178
    }
179
180
    // Apply constants to the definition
181
    var constants = {};
182
    for (i in prototype.__joii__.constants) {
183
        if (prototype.__joii__.constants.hasOwnProperty(i) === false) continue;
184
        JOII.CreateProperty(constructor, i, prototype.__joii__.constants[i], false);
185
        constants[i] = prototype.__joii__.constants[i];
186
    }
187
188
    // Does the class implement an enumerator?
189
    if (typeof (parameters['enum']) === 'string') {
190
        var e = JOII.EnumBuilder(parameters['enum'], constants);
191
        if (parameters.expose_enum === true) {
192
            var g = typeof window === 'object' ? window : global;
193
            if (typeof (g[parameters['enum']]) !== 'undefined') {
194
                throw 'Cannot expose Enum "' + parameters['enum'] + '" becase it already exists in the global scope.';
195
            }
196
            g[parameters['enum']] = e;
197
        }
198
    }
199
    JOII.InterfaceRegistry[name] = constructor;
200
201
    return constructor;
202
};
203