1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Validator = void 0;
|
4 | const assert = require("node:assert");
|
5 | const spec = require("@jsii/spec");
|
6 | const deepEqual = require("fast-deep-equal");
|
7 | const ts = require("typescript");
|
8 | const Case = require("./case");
|
9 | const jsii_diagnostic_1 = require("./jsii-diagnostic");
|
10 | const node_bindings_1 = require("./node-bindings");
|
11 | const bindings = require("./node-bindings");
|
12 | class 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 | }
|
28 | exports.Validator = Validator;
|
29 | Validator.VALIDATIONS = _defaultValidations();
|
30 | function _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,
|
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 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
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 |
|
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 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | const memberCopy = { ...member };
|
162 |
|
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 |
|
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 |
|
219 |
|
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 |
|
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 |
|
243 |
|
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 |
|
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 | }
|
338 | function _allTypes(assm) {
|
339 | return Object.values(assm.types ?? {});
|
340 | }
|
341 | function _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 | }
|
355 | function _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 | }
|
369 | function _allMembers(assm) {
|
370 | return [..._allMethods(assm), ..._allProperties(assm)];
|
371 | }
|
372 | function _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 | }
|
428 | function _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 | }
|
439 | function _isEmpty(array) {
|
440 | return array == null || array.length === 0;
|
441 | }
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 | function isConstantCase(x) {
|
451 | return !/[^A-Z0-9_]/.exec(x);
|
452 | }
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 | function declarationName(decl) {
|
467 | if (decl == null) {
|
468 |
|
469 | return decl;
|
470 | }
|
471 | return ts.getNameOfDeclaration(decl) ?? decl;
|
472 | }
|
473 |
|
\ | No newline at end of file |