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
|
|||
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. ![]() |
|||
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 |
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.