diff --git a/packages/jsii/jest.config.mjs b/packages/jsii/jest.config.mjs index 7bc6eadd1b..dea89eb8a3 100644 --- a/packages/jsii/jest.config.mjs +++ b/packages/jsii/jest.config.mjs @@ -1,3 +1,5 @@ -import config from '../../jest.config.mjs'; +import { overriddenConfig } from '../../jest.config.mjs'; -export default config; +export default overriddenConfig({ + watchPathIgnorePatterns: ['.*\\.tsx?$'], +}); diff --git a/packages/jsii/lib/transforms/deprecation-warnings.ts b/packages/jsii/lib/transforms/deprecation-warnings.ts index a0a7b2f36a..ddbdc37014 100644 --- a/packages/jsii/lib/transforms/deprecation-warnings.ts +++ b/packages/jsii/lib/transforms/deprecation-warnings.ts @@ -10,6 +10,7 @@ import { symbolIdentifier } from '../symbol-id'; export const WARNINGSCODE_FILE_NAME = '.warnings.jsii.js'; const WARNING_FUNCTION_NAME = 'print'; const PARAMETER_NAME = 'p'; +const FOR_LOOP_ITEM_NAME = 'o'; const NAMESPACE = 'jsiiDeprecationWarnings'; const LOCAL_ENUM_NAMESPACE = 'ns'; const VISITED_OBJECTS_SET_NAME = 'visitedObjects'; @@ -236,6 +237,7 @@ function processInterfaceType( const statement = createTypeHandlerCall( functionName, `${PARAMETER_NAME}.${prop.name}`, + prop.type.collection.kind, ); statementsByProp.set(`${prop.name}_`, statement); } @@ -718,25 +720,67 @@ function findType(typeName: string, assemblies: Assembly[]) { function createTypeHandlerCall( functionName: string, parameter: string, + collectionKind?: spec.CollectionKind, ): ts.Statement { - return ts.createIf( - ts.createPrefix( - ts.SyntaxKind.ExclamationToken, - ts.createCall( - ts.createPropertyAccess( - ts.createIdentifier(VISITED_OBJECTS_SET_NAME), - ts.createIdentifier('has'), + switch (collectionKind) { + case spec.CollectionKind.Array: + return ts.createIf( + ts.createBinary( + ts.createIdentifier(parameter), + ts.SyntaxKind.ExclamationEqualsToken, + ts.createNull(), ), - undefined, - [ts.createIdentifier(parameter)], - ), - ), - ts.createExpressionStatement( - ts.createCall(ts.createIdentifier(functionName), undefined, [ - ts.createIdentifier(parameter), - ]), - ), - ); + ts.createForOf( + undefined, + ts.createVariableDeclarationList( + [ts.createVariableDeclaration(FOR_LOOP_ITEM_NAME)], + ts.NodeFlags.Const, + ), + ts.createIdentifier(parameter), + createTypeHandlerCall(functionName, FOR_LOOP_ITEM_NAME), + ), + ); + case spec.CollectionKind.Map: + return ts.createIf( + ts.createBinary( + ts.createIdentifier(parameter), + ts.SyntaxKind.ExclamationEqualsToken, + ts.createNull(), + ), + ts.createForOf( + undefined, + ts.createVariableDeclarationList( + [ts.createVariableDeclaration(FOR_LOOP_ITEM_NAME)], + ts.NodeFlags.Const, + ), + ts.createCall( + ts.createPropertyAccess(ts.createIdentifier('Object'), 'values'), + undefined, + [ts.createIdentifier(parameter)], + ), + createTypeHandlerCall(functionName, FOR_LOOP_ITEM_NAME), + ), + ); + case undefined: + return ts.createIf( + ts.createPrefix( + ts.SyntaxKind.ExclamationToken, + ts.createCall( + ts.createPropertyAccess( + ts.createIdentifier(VISITED_OBJECTS_SET_NAME), + ts.createIdentifier('has'), + ), + undefined, + [ts.createIdentifier(parameter)], + ), + ), + ts.createExpressionStatement( + ts.createCall(ts.createIdentifier(functionName), undefined, [ + ts.createIdentifier(parameter), + ]), + ), + ); + } } /** diff --git a/packages/jsii/test/deprecation-warnings.test.ts b/packages/jsii/test/deprecation-warnings.test.ts index b62541b8e1..56c368dbb1 100644 --- a/packages/jsii/test/deprecation-warnings.test.ts +++ b/packages/jsii/test/deprecation-warnings.test.ts @@ -179,6 +179,64 @@ function testpkg_Baz(p) { ); }); + test('checks array elements', () => { + const result = compileJsiiForTest( + ` + export interface Used { readonly property: boolean; } + export interface Uses { readonly array: Used[]; } + `, + undefined /* callback */, + { addDeprecationWarnings: true }, + ); + + expect(jsFunction(result, 'testpkg_Uses', '.warnings.jsii')) + .toMatchInlineSnapshot(` + "function testpkg_Uses(p) { + if (p == null) + return; + visitedObjects.add(p); + try { + if (p.array != null) + for (const o of p.array) + if (!visitedObjects.has(o)) + testpkg_Used(o); + } + finally { + visitedObjects.delete(p); + } + }" + `); + }); + + test('checks map elements', () => { + const result = compileJsiiForTest( + ` + export interface Used { readonly property: boolean; } + export interface Uses { readonly map: Record; } + `, + undefined /* callback */, + { addDeprecationWarnings: true }, + ); + + expect(jsFunction(result, 'testpkg_Uses', '.warnings.jsii')) + .toMatchInlineSnapshot(` + "function testpkg_Uses(p) { + if (p == null) + return; + visitedObjects.add(p); + try { + if (p.map != null) + for (const o of Object.values(p.map)) + if (!visitedObjects.has(o)) + testpkg_Used(o); + } + finally { + visitedObjects.delete(p); + } + }" + `); + }); + test('generates exports for all the functions', () => { const result = compileJsiiForTest( ` @@ -862,6 +920,24 @@ function jsFile(result: HelperCompilationResult, baseName = 'index'): string { return file[1]; } +function jsFunction( + result: HelperCompilationResult, + functionName: string, + baseName = 'index', +): string { + const lines = jsFile(result, baseName).split(/\n/); + + const startIndex = lines.indexOf(`function ${functionName}(p) {`); + if (startIndex < 0) { + throw new Error( + `Could not find declaration of ${functionName} in file with base name: ${baseName}`, + ); + } + const endIndex = lines.indexOf('}', startIndex); + + return lines.slice(startIndex, endIndex + 1).join('\n'); +} + function createVmContext(compilation: HelperCompilationResult) { const context = vm.createContext({ exports: {},