UNPKG

23.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Validator = void 0;
4const assert = require("node:assert");
5const spec = require("@jsii/spec");
6const deepEqual = require("fast-deep-equal");
7const ts = require("typescript");
8const Case = require("./case");
9const jsii_diagnostic_1 = require("./jsii-diagnostic");
10const node_bindings_1 = require("./node-bindings");
11const bindings = require("./node-bindings");
12class Validator {
13 constructor(projectInfo, assembly) {
14 this.projectInfo = projectInfo;
15 this.assembly = assembly;
16 }
17 emit() {
18 const diagnostics = new Array();
19 for (const validation of Validator.VALIDATIONS) {
20 validation(this, this.assembly, diagnostics.push.bind(diagnostics));
21 }
22 return {
23 diagnostics: diagnostics,
24 emitSkipped: diagnostics.some((diag) => diag.category === ts.DiagnosticCategory.Error),
25 };
26 }
27}
28exports.Validator = Validator;
29Validator.VALIDATIONS = _defaultValidations();
30function _defaultValidations() {
31 return [
32 _enumMembersMustUserUpperSnakeCase,
33 _memberNamesMustUseCamelCase,
34 _staticConstantNamesMustUseUpperSnakeCase,
35 _memberNamesMustNotLookLikeJavaGettersOrSetters,
36 _allTypeReferencesAreValid,
37 _inehritanceDoesNotChangeContracts,
38 _staticMembersAndNestedTypesMustNotSharePascalCaseName,
39 ];
40 function _enumMembersMustUserUpperSnakeCase(_, assembly, diagnostic) {
41 for (const type of _allTypes(assembly)) {
42 if (!spec.isEnumType(type)) {
43 continue;
44 }
45 for (const member of type.members) {
46 if (member.name && !isConstantCase(member.name)) {
47 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_8001_ALL_CAPS_ENUM_MEMBERS.createDetached(member.name, type.fqn));
48 }
49 }
50 }
51 }
52 function _memberNamesMustUseCamelCase(_, assembly, diagnostic) {
53 for (const { member, type } of _allMembers(assembly)) {
54 if (member.static && member.const) {
55 continue;
56 }
57 if (member.name && member.name !== Case.camel(member.name)) {
58 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_8002_CAMEL_CASED_MEMBERS.createDetached(member.name, type.fqn));
59 }
60 }
61 }
62 function _staticConstantNamesMustUseUpperSnakeCase(_, assembly, diagnostic) {
63 for (const { member, type } of _allMembers(assembly)) {
64 if (!member.static || !member.const) {
65 continue;
66 }
67 if (member.name &&
68 !isConstantCase(member.name) &&
69 member.name !== Case.pascal(member.name) &&
70 member.name !== Case.camel(member.name)) {
71 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_8003_STATIC_CONST_CASING.createDetached(member.name, type.name));
72 }
73 }
74 }
75 function _memberNamesMustNotLookLikeJavaGettersOrSetters(_, assembly, diagnostic) {
76 for (const { member, type } of _allMembers(assembly)) {
77 if (!member.name) {
78 continue;
79 }
80 const snakeName = Case.snake(member.name);
81 if (snakeName.startsWith('get_') && _isEmpty(member.parameters)) {
82 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5000_JAVA_GETTERS.createDetached(member.name, type.name));
83 }
84 else if (snakeName.startsWith('set_') && (member.parameters ?? []).length === 1) {
85 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5001_JAVA_SETTERS.createDetached(member.name, type.name));
86 }
87 }
88 }
89 function _allTypeReferencesAreValid(validator, assembly, diagnostic) {
90 for (const typeRef of _allTypeReferences(assembly)) {
91 const [assm] = typeRef.fqn.split('.');
92 if (assembly.name === assm) {
93 if (!(typeRef.fqn in (assembly.types ?? {}))) {
94 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_3000_EXPORTED_API_USES_HIDDEN_TYPE.create(typeRef.node, // Pretend there is always a value
95 typeRef.fqn));
96 }
97 continue;
98 }
99 const foreignAssm = validator.projectInfo.dependencyClosure.find((dep) => dep.name === assm);
100 if (!foreignAssm) {
101 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_9000_UNKNOWN_MODULE.createDetached(assm));
102 continue;
103 }
104 if (!(typeRef.fqn in (foreignAssm.types ?? {}))) {
105 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_9001_TYPE_NOT_FOUND.createDetached(typeRef));
106 }
107 }
108 }
109 function _inehritanceDoesNotChangeContracts(validator, assembly, diagnostic) {
110 for (const type of _allTypes(assembly)) {
111 if (spec.isClassType(type)) {
112 for (const method of type.methods ?? []) {
113 _validateMethodOverride(method, type);
114 }
115 for (const property of type.properties ?? []) {
116 _validatePropertyOverride(property, type);
117 }
118 }
119 if (spec.isClassOrInterfaceType(type) && (type.interfaces?.length ?? 0) > 0) {
120 for (const method of _allImplementations(type, (t) => t.methods)) {
121 _validateMethodImplementation(method, type);
122 }
123 for (const property of _allImplementations(type, (t) => t.properties)) {
124 _validatePropertyImplementation(property, type);
125 }
126 }
127 }
128 /**
129 * Lists all "implementations" from the given type, using the provided
130 * implementation getter. Note that abstract members may be part of the
131 * result (in particular, if `type` is an interface type, or if it's an
132 * abstract class with unimplemented members) -- I just couldn't come up
133 * with a name that actually describes this.
134 *
135 * @param type the type which implemented members are needed.
136 * @param getter the getter to obtain methods or properties from the type.
137 *
138 * @returns a list of members (possibly empty, always defined)
139 */
140 function _allImplementations(type, getter) {
141 const result = new Array();
142 const known = new Set();
143 for (const member of getter(type) ?? []) {
144 result.push(member);
145 known.add(member.name);
146 }
147 if (spec.isClassType(type) && type.base) {
148 // We have a parent class, collect their concrete members, too (recursively)...
149 const base = _dereference(type.base, assembly, validator);
150 assert(base != null && spec.isClassType(base));
151 for (const member of _allImplementations(base, getter)) {
152 if (known.has(member.name)) {
153 continue;
154 }
155 // The member is copied, so that its `overrides` property won't be
156 // altered, since this member is "borrowed" from a parent type. We
157 // only check it, but should not record `overrides` relationships to
158 // it as those could be invalid per the parent type (i.e: the parent
159 // member may not be able to implement an interface, if that type does
160 // not actually declare implementing that).
161 const memberCopy = { ...member };
162 // Forward the related node if there's one, so diagnostics are bound.
163 const node = bindings.getRelatedNode(member);
164 if (node != null) {
165 bindings.setRelatedNode(memberCopy, node);
166 }
167 result.push(memberCopy);
168 known.add(member.name);
169 }
170 }
171 return result;
172 }
173 function _validateMethodOverride(method, type) {
174 if (!type.base) {
175 return false;
176 }
177 const baseType = _dereference(type.base, assembly, validator);
178 if (!baseType) {
179 return false;
180 }
181 const overridden = (baseType.methods ?? []).find((m) => m.name === method.name);
182 if (!overridden) {
183 return _validateMethodOverride(method, baseType);
184 }
185 _assertSignaturesMatch(overridden, method, `${type.fqn}#${method.name}`, `overriding ${baseType.fqn}`);
186 method.overrides = baseType.fqn;
187 return true;
188 }
189 function _validatePropertyOverride(property, type) {
190 if (!type.base) {
191 return false;
192 }
193 const baseType = _dereference(type.base, assembly, validator);
194 if (!baseType) {
195 return false;
196 }
197 const overridden = (baseType.properties ?? []).find((p) => p.name === property.name);
198 if (!overridden) {
199 return _validatePropertyOverride(property, baseType);
200 }
201 _assertPropertiesMatch(overridden, property, `${type.fqn}#${property.name}`, `overriding ${baseType.fqn}`);
202 property.overrides = baseType.fqn;
203 return true;
204 }
205 function _validateMethodImplementation(method, type) {
206 if (!type.interfaces) {
207 // Abstract classes may not directly implement all members, need to check their supertypes...
208 if (spec.isClassType(type) && type.base && type.abstract) {
209 return _validateMethodImplementation(method, _dereference(type.base, assembly, validator));
210 }
211 return false;
212 }
213 for (const iface of type.interfaces) {
214 const ifaceType = _dereference(iface, assembly, validator);
215 const implemented = (ifaceType.methods ?? []).find((m) => m.name === method.name);
216 if (implemented) {
217 _assertSignaturesMatch(implemented, method, `${type.fqn}#${method.name}`, `implementing ${ifaceType.fqn}`);
218 // We won't replace a previous overrides declaration from a method override, as those have
219 // higher precedence than an initial implementation.
220 method.overrides = method.overrides ?? iface;
221 return true;
222 }
223 if (_validateMethodImplementation(method, ifaceType)) {
224 return true;
225 }
226 }
227 return false;
228 }
229 function _validatePropertyImplementation(property, type) {
230 if (!type.interfaces) {
231 // Abstract classes may not directly implement all members, need to check their supertypes...
232 if (spec.isClassType(type) && type.base && type.abstract) {
233 return _validatePropertyImplementation(property, _dereference(type.base, assembly, validator));
234 }
235 return false;
236 }
237 for (const iface of type.interfaces) {
238 const ifaceType = _dereference(iface, assembly, validator);
239 const implemented = (ifaceType.properties ?? []).find((p) => p.name === property.name);
240 if (implemented) {
241 _assertPropertiesMatch(implemented, property, `${type.fqn}#${property.name}`, `implementing ${ifaceType.fqn}`);
242 // We won't replace a previous overrides declaration from a property override, as those
243 // have higher precedence than an initial implementation.
244 property.overrides = property.overrides ?? ifaceType.fqn;
245 return true;
246 }
247 if (_validatePropertyImplementation(property, ifaceType)) {
248 return true;
249 }
250 }
251 return false;
252 }
253 function _assertSignaturesMatch(expected, actual, label, action) {
254 if (!!expected.protected !== !!actual.protected) {
255 const expVisibility = expected.protected ? 'protected' : 'public';
256 const actVisibility = actual.protected ? 'protected' : 'public';
257 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5002_OVERRIDE_CHANGES_VISIBILITY.createDetached(label, action, actVisibility, expVisibility));
258 }
259 if (!deepEqual(actual.returns, expected.returns)) {
260 const expType = spec.describeTypeReference(expected.returns?.type);
261 const actType = spec.describeTypeReference(actual.returns?.type);
262 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5003_OVERRIDE_CHANGES_RETURN_TYPE.createDetached(label, action, actType, expType));
263 }
264 const expectedParams = expected.parameters ?? [];
265 const actualParams = actual.parameters ?? [];
266 if (expectedParams.length !== actualParams.length) {
267 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5005_OVERRIDE_CHANGES_PARAM_COUNT.createDetached(label, action, actualParams.length, expectedParams.length));
268 return;
269 }
270 for (let i = 0; i < expectedParams.length; i++) {
271 const expParam = expectedParams[i];
272 const actParam = actualParams[i];
273 if (!deepEqual(expParam.type, actParam.type)) {
274 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5006_OVERRIDE_CHANGES_PARAM_TYPE.createDetached(label, action, actParam, expParam));
275 }
276 // Not-ing those to force the values to a strictly boolean context (they're optional, undefined means false)
277 if (expParam.variadic !== actParam.variadic) {
278 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5007_OVERRIDE_CHANGES_VARIADIC.createDetached(label, action, actParam.variadic, expParam.variadic));
279 }
280 if (expParam.optional !== actParam.optional) {
281 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5008_OVERRIDE_CHANGES_PARAM_OPTIONAL.createDetached(label, action, actParam, expParam));
282 }
283 }
284 }
285 function _assertPropertiesMatch(expected, actual, label, action) {
286 const actualNode = bindings.getPropertyRelatedNode(actual);
287 const expectedNode = bindings.getPropertyRelatedNode(expected);
288 if (!!expected.protected !== !!actual.protected) {
289 const expVisibility = expected.protected ? 'protected' : 'public';
290 const actVisibility = actual.protected ? 'protected' : 'public';
291 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5002_OVERRIDE_CHANGES_VISIBILITY.create(actualNode?.modifiers?.find((mod) => mod.kind === ts.SyntaxKind.PublicKeyword || mod.kind === ts.SyntaxKind.ProtectedKeyword) ?? declarationName(actualNode), label, action, actVisibility, expVisibility).maybeAddRelatedInformation(expectedNode?.modifiers?.find((mod) => mod.kind === ts.SyntaxKind.PublicKeyword || mod.kind === ts.SyntaxKind.ProtectedKeyword) ?? declarationName(expectedNode), 'The implemented delcaration is here.'));
292 }
293 if (!deepEqual(expected.type, actual.type)) {
294 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5004_OVERRIDE_CHANGES_PROP_TYPE.create(actualNode?.type ?? declarationName(actualNode), label, action, actual.type, expected.type).maybeAddRelatedInformation(expectedNode?.type ?? declarationName(expectedNode), 'The implemented delcaration is here.'));
295 }
296 if (expected.immutable !== actual.immutable) {
297 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5010_OVERRIDE_CHANGES_MUTABILITY.create(actualNode?.modifiers?.find((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword) ??
298 declarationName(actualNode), label, action, actual.immutable, expected.immutable).maybeAddRelatedInformation(expectedNode?.modifiers?.find((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword) ??
299 declarationName(expectedNode), 'The implemented delcaration is here.'));
300 }
301 if (expected.optional !== actual.optional) {
302 diagnostic(jsii_diagnostic_1.JsiiDiagnostic.JSII_5009_OVERRIDE_CHANGES_PROP_OPTIONAL.create(actualNode?.questionToken ?? actualNode?.type ?? declarationName(actualNode), label, action, actual.optional, expected.optional).maybeAddRelatedInformation(expectedNode?.questionToken ?? expectedNode?.type ?? declarationName(expectedNode), 'The implemented delcaration is here.'));
303 }
304 }
305 }
306 function _staticMembersAndNestedTypesMustNotSharePascalCaseName(_, assembly, diagnostic) {
307 for (const nestedType of Object.values(assembly.types ?? {})) {
308 if (nestedType.namespace == null) {
309 continue;
310 }
311 const nestingType = assembly.types[`${assembly.name}.${nestedType.namespace}`];
312 if (nestingType == null) {
313 continue;
314 }
315 const nestedTypeName = Case.pascal(nestedType.name);
316 for (const { name, member } of staticMembers(nestingType)) {
317 if (name === nestedTypeName) {
318 let diag = jsii_diagnostic_1.JsiiDiagnostic.JSII_5020_STATIC_MEMBER_CONFLICTS_WITH_NESTED_TYPE.create((0, node_bindings_1.getRelatedNode)(member), nestingType, member, nestedType);
319 const nestedTypeNode = (0, node_bindings_1.getRelatedNode)(nestedType);
320 if (nestedTypeNode != null) {
321 diag = diag.addRelatedInformation(nestedTypeNode, 'This is the conflicting nested type declaration');
322 }
323 diagnostic(diag);
324 }
325 }
326 }
327 function staticMembers(type) {
328 if (spec.isClassOrInterfaceType(type)) {
329 return [
330 ...(type.methods?.filter((method) => method.static) ?? []),
331 ...(type.properties?.filter((prop) => prop.static) ?? []),
332 ].map((member) => ({ name: Case.pascal(member.name), member }));
333 }
334 return type.members.map((member) => ({ name: member.name, member }));
335 }
336 }
337}
338function _allTypes(assm) {
339 return Object.values(assm.types ?? {});
340}
341function _allMethods(assm) {
342 const methods = new Array();
343 for (const type of _allTypes(assm)) {
344 if (!spec.isClassOrInterfaceType(type)) {
345 continue;
346 }
347 if (!type.methods) {
348 continue;
349 }
350 for (const method of type.methods)
351 methods.push({ member: method, type });
352 }
353 return methods;
354}
355function _allProperties(assm) {
356 const properties = new Array();
357 for (const type of _allTypes(assm)) {
358 if (!spec.isClassOrInterfaceType(type)) {
359 continue;
360 }
361 if (!type.properties) {
362 continue;
363 }
364 for (const property of type.properties)
365 properties.push({ member: property, type });
366 }
367 return properties;
368}
369function _allMembers(assm) {
370 return [..._allMethods(assm), ..._allProperties(assm)];
371}
372function _allTypeReferences(assm) {
373 const typeReferences = new Array();
374 for (const type of _allTypes(assm)) {
375 if (!spec.isClassOrInterfaceType(type)) {
376 continue;
377 }
378 if (spec.isClassType(type)) {
379 const node = bindings.getClassRelatedNode(type);
380 if (type.base) {
381 typeReferences.push({
382 fqn: type.base,
383 node: node?.heritageClauses?.find((hc) => hc.token === ts.SyntaxKind.ExtendsKeyword)?.types[0],
384 });
385 }
386 if (type.initializer?.parameters) {
387 for (const param of type.initializer.parameters) {
388 _collectTypeReferences(param.type, bindings.getParameterRelatedNode(param)?.type);
389 }
390 }
391 }
392 if (type.interfaces) {
393 const node = bindings.getClassOrInterfaceRelatedNode(type);
394 for (const iface of type.interfaces) {
395 typeReferences.push({
396 fqn: iface,
397 node: node?.heritageClauses?.find((hc) => hc.token ===
398 (spec.isInterfaceType(type) ? ts.SyntaxKind.ImplementsKeyword : ts.SyntaxKind.ExtendsKeyword)),
399 });
400 }
401 }
402 }
403 for (const { member: prop } of _allProperties(assm)) {
404 _collectTypeReferences(prop.type, bindings.getPropertyRelatedNode(prop)?.type);
405 }
406 for (const { member: meth } of _allMethods(assm)) {
407 if (meth.returns) {
408 _collectTypeReferences(meth.returns.type, bindings.getMethodRelatedNode(meth)?.type);
409 }
410 for (const param of meth.parameters ?? []) {
411 _collectTypeReferences(param.type, bindings.getParameterRelatedNode(param)?.type);
412 }
413 }
414 return typeReferences;
415 function _collectTypeReferences(type, node) {
416 if (spec.isNamedTypeReference(type)) {
417 typeReferences.push({ ...type, node });
418 }
419 else if (spec.isCollectionTypeReference(type)) {
420 _collectTypeReferences(type.collection.elementtype, node);
421 }
422 else if (spec.isUnionTypeReference(type)) {
423 for (const t of type.union.types)
424 _collectTypeReferences(t, node);
425 }
426 }
427}
428function _dereference(typeRef, assembly, validator) {
429 if (typeof typeRef !== 'string') {
430 typeRef = typeRef.fqn;
431 }
432 const [assm] = typeRef.split('.');
433 if (assembly.name === assm) {
434 return assembly.types?.[typeRef];
435 }
436 const foreignAssm = validator.projectInfo.dependencyClosure.find((dep) => dep.name === assm);
437 return foreignAssm?.types?.[typeRef];
438}
439function _isEmpty(array) {
440 return array == null || array.length === 0;
441}
442/**
443 * Return whether an identifier only consists of upperchase characters, digits and underscores
444 *
445 * We have our own check here (isConstantCase) which is more lenient than what
446 * `case.constant()` prescribes. We also want to allow combinations of letters
447 * and digits without underscores: `C5A`, which `case` would force to `C5_A`.
448 * The hint we print will still use `case.constant()` but that is fine.
449 */
450function isConstantCase(x) {
451 return !/[^A-Z0-9_]/.exec(x);
452}
453/**
454 * Obtains the name of the given declaration, if it has one, or returns the declaration itself.
455 * This function is meant to be used as a convenience to obtain the `ts.Node` to bind a
456 * `JsiiDianostic` instance on.
457 *
458 * It may return `undefined` but is typed as `ts.Node` so that it is easier to use with
459 * `JsiiDiagnostic` factories.
460 *
461 * @param decl the declaration which name is needed.
462 *
463 * @returns the name of the declaration if it has one, or the declaration itself. Might return
464 * `undefined` if the provided declaration is undefined.
465 */
466function declarationName(decl) {
467 if (decl == null) {
468 // Pretend we returned a node - this is used to create diagnostics, worst case it'll be unbound.
469 return decl;
470 }
471 return ts.getNameOfDeclaration(decl) ?? decl;
472}
473//# sourceMappingURL=validator.js.map
\No newline at end of file