Skip to content

Commit 9768e2a

Browse files
authored
fix(rosetta): rosetta reader expects default tablet file to be uncompressed (#3723)
Due to a bug in `rosetta-reader.ts`, we always expect to find the implicit tablet at `.jsii.tabl.json`. This means that any compressed implicit tablets will be unable to be used as a cache, and causes the following failure: ```bash The following snippet was not found in any of the loaded tablets: ... ``` This PR also slightly changes the default behavior of `compressTablet`; previously it was `false` by default. Now it respects the original compression status of the implicit tablet, i.e. if it was previously compressed, then `extract` will update it as compressed. --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
1 parent 93aec85 commit 9768e2a

File tree

5 files changed

+142
-15
lines changed

5 files changed

+142
-15
lines changed

packages/jsii-rosetta/lib/commands/extract.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as path from 'path';
22

3-
import { loadAssemblies, allTypeScriptSnippets, loadAllDefaultTablets } from '../jsii/assemblies';
3+
import {
4+
loadAssemblies,
5+
allTypeScriptSnippets,
6+
loadAllDefaultTablets,
7+
compressedTabletExists,
8+
} from '../jsii/assemblies';
49
import * as logging from '../logging';
510
import { RosettaTranslator, RosettaTranslatorOptions } from '../rosetta-translator';
611
import { TypeScriptSnippet, SnippetParameters } from '../snippet';
@@ -75,9 +80,9 @@ export interface ExtractOptions {
7580
readonly allowDirtyTranslations?: boolean;
7681

7782
/**
78-
* Compress the implicit tablet files
83+
* Compress the implicit tablet files.
7984
*
80-
* @default false
85+
* @default - preserves the original compression status of each individual implicit tablet file.
8186
*/
8287
readonly compressTablet?: boolean;
8388

@@ -169,16 +174,19 @@ export async function extractSnippets(
169174
if (options.writeToImplicitTablets ?? true) {
170175
await Promise.all(
171176
Object.entries(snippetsPerAssembly).map(async ([location, snips]) => {
177+
// Compress the implicit tablet if explicitly asked to, otherwise compress only if the original tablet was compressed.
178+
const compressedTablet = options.compressTablet ?? compressedTabletExists(location);
179+
172180
const asmTabletFile = path.join(
173181
location,
174-
options.compressTablet ? DEFAULT_TABLET_NAME_COMPRESSED : DEFAULT_TABLET_NAME,
182+
compressedTablet ? DEFAULT_TABLET_NAME_COMPRESSED : DEFAULT_TABLET_NAME,
175183
);
176184
logging.debug(`Writing ${snips.length} translations to ${asmTabletFile}`);
177185
const translations = snips.map(({ key }) => translator.tablet.tryGetSnippet(key)).filter(isDefined);
178186

179187
const asmTablet = new LanguageTablet();
180188
asmTablet.addSnippets(...translations);
181-
await asmTablet.save(asmTabletFile, options.compressTablet);
189+
await asmTablet.save(asmTabletFile, compressedTablet);
182190
}),
183191
);
184192
}

packages/jsii-rosetta/lib/jsii/assemblies.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,26 @@ export function loadAssemblies(
8383
export async function loadAllDefaultTablets(asms: readonly LoadedAssembly[]): Promise<Record<string, LanguageTablet>> {
8484
return mkDict(
8585
await Promise.all(
86-
asms.map(async (a) => [a.directory, await LanguageTablet.fromOptionalFile(guessTabletLocation(a))] as const),
86+
asms.map(
87+
async (a) => [a.directory, await LanguageTablet.fromOptionalFile(guessTabletLocation(a.directory))] as const,
88+
),
8789
),
8890
);
91+
}
8992

90-
function guessTabletLocation(a: LoadedAssembly): string {
91-
const defaultTablet = path.join(a.directory, DEFAULT_TABLET_NAME);
92-
const compDefaultTablet = path.join(a.directory, DEFAULT_TABLET_NAME_COMPRESSED);
93-
return fs.existsSync(defaultTablet) ? defaultTablet : compDefaultTablet;
94-
}
93+
/**
94+
* Returns the location of the tablet file, either .jsii.tabl.json or .jsii.tabl.json.gz.
95+
* Assumes that a tablet exists in the directory and if not, the ensuing behavior is
96+
* handled by the caller of this function.
97+
*/
98+
export function guessTabletLocation(directory: string) {
99+
return compressedTabletExists(directory)
100+
? path.join(directory, DEFAULT_TABLET_NAME_COMPRESSED)
101+
: path.join(directory, DEFAULT_TABLET_NAME);
102+
}
103+
104+
export function compressedTabletExists(directory: string) {
105+
return fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME_COMPRESSED));
95106
}
96107

97108
export type AssemblySnippetSource =

packages/jsii-rosetta/lib/rosetta-reader.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as spec from '@jsii/spec';
2-
import * as path from 'path';
32

4-
import { allTypeScriptSnippets } from './jsii/assemblies';
3+
import { allTypeScriptSnippets, guessTabletLocation } from './jsii/assemblies';
54
import { TargetLanguage } from './languages';
65
import * as logging from './logging';
76
import { transformMarkdown } from './markdown/markdown';
@@ -16,7 +15,7 @@ import {
1615
typeScriptSnippetFromSource,
1716
} from './snippet';
1817
import { snippetKey } from './tablets/key';
19-
import { DEFAULT_TABLET_NAME, LanguageTablet, Translation } from './tablets/tablets';
18+
import { LanguageTablet, Translation } from './tablets/tablets';
2019
import { Translator } from './translate';
2120
import { commentToken, pathExists, printDiagnostics } from './util';
2221

@@ -148,7 +147,7 @@ export class RosettaTabletReader {
148147
* pacmak sends our way later on is not going to be enough to do that.
149148
*/
150149
public async addAssembly(assembly: spec.Assembly, assemblyDir: string) {
151-
const defaultTablet = path.join(assemblyDir, DEFAULT_TABLET_NAME);
150+
const defaultTablet = guessTabletLocation(assemblyDir);
152151
if (await pathExists(defaultTablet)) {
153152
try {
154153
await this.loadTabletFromFile(defaultTablet);

packages/jsii-rosetta/test/commands/extract.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,78 @@ test('extract can compress implicit tablet file', async () => {
106106
expect(tablet.snippetKeys.length).toEqual(1);
107107
});
108108

109+
describe('extract behavior regarding compressing implicit tablets', () => {
110+
function hasCompressedDefaultTablet(directory: string) {
111+
return (
112+
fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME_COMPRESSED)) &&
113+
!fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME))
114+
);
115+
}
116+
117+
function hasUncompressedDefaultTablet(directory: string) {
118+
return (
119+
!fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME_COMPRESSED)) &&
120+
fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME))
121+
);
122+
}
123+
124+
function hasBothDefaultTablets(directory: string) {
125+
return (
126+
fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME_COMPRESSED)) &&
127+
fs.existsSync(path.join(directory, DEFAULT_TABLET_NAME))
128+
);
129+
}
130+
131+
test('preserve uncompressed tablet by default', async () => {
132+
// Run extract with compressTablet = false to create .jsii.tabl.json file
133+
await extract.extractSnippets([assembly.moduleDirectory], {
134+
...defaultExtractOptions,
135+
compressTablet: false,
136+
});
137+
138+
expect(hasUncompressedDefaultTablet(assembly.moduleDirectory)).toBeTruthy();
139+
140+
await extract.extractSnippets([assembly.moduleDirectory], {
141+
...defaultExtractOptions,
142+
});
143+
144+
expect(hasUncompressedDefaultTablet(assembly.moduleDirectory)).toBeTruthy();
145+
});
146+
147+
test('preserve compressed tablet by default', async () => {
148+
// Run extract with compressTablet = true to create .jsii.tabl.json.gz file
149+
await extract.extractSnippets([assembly.moduleDirectory], {
150+
...defaultExtractOptions,
151+
compressTablet: true,
152+
});
153+
154+
expect(hasCompressedDefaultTablet(assembly.moduleDirectory)).toBeTruthy();
155+
156+
await extract.extractSnippets([assembly.moduleDirectory], {
157+
...defaultExtractOptions,
158+
});
159+
160+
expect(hasCompressedDefaultTablet(assembly.moduleDirectory)).toBeTruthy();
161+
});
162+
163+
test('respects the explicit call to compress the tablet', async () => {
164+
// Run extract with compressTablet = false to create .jsii.tabl.json file
165+
await extract.extractSnippets([assembly.moduleDirectory], {
166+
...defaultExtractOptions,
167+
compressTablet: false,
168+
});
169+
170+
expect(hasUncompressedDefaultTablet(assembly.moduleDirectory)).toBeTruthy();
171+
172+
await extract.extractSnippets([assembly.moduleDirectory], {
173+
...defaultExtractOptions,
174+
compressTablet: true,
175+
});
176+
177+
expect(hasBothDefaultTablets(assembly.moduleDirectory)).toBeTruthy();
178+
});
179+
});
180+
109181
test('extract works from compressed test assembly', async () => {
110182
const compressedAssembly = TestJsiiModule.fromSource(
111183
{

packages/jsii-rosetta/test/commands/transliterate.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,43 @@ test('will read translations from cache even if they are dirty', async () => {
13871387
}
13881388
});
13891389

1390+
test('will read translations from cache when tablet is compressed', async () => {
1391+
const infusedAssembly = TestJsiiModule.fromSource(
1392+
{
1393+
'index.ts': `
1394+
/**
1395+
* ClassA
1396+
*
1397+
* @example x
1398+
*/
1399+
export class ClassA {
1400+
public someMethod() {
1401+
}
1402+
}
1403+
`,
1404+
},
1405+
{
1406+
name: 'my_assembly',
1407+
jsii: DUMMY_JSII_CONFIG,
1408+
},
1409+
);
1410+
try {
1411+
// Run an extract and tell it to compress the tablet
1412+
await extractSnippets([infusedAssembly.moduleDirectory], {
1413+
compressTablet: true,
1414+
});
1415+
1416+
await transliterateAssembly([infusedAssembly.moduleDirectory], [TargetLanguage.PYTHON]);
1417+
1418+
const translated: Assembly = JSON.parse(
1419+
await fs.promises.readFile(path.join(infusedAssembly.moduleDirectory, '.jsii.python'), 'utf-8'),
1420+
);
1421+
expect(translated.types?.['my_assembly.ClassA'].docs?.example).toEqual('x');
1422+
} finally {
1423+
infusedAssembly.cleanup();
1424+
}
1425+
});
1426+
13901427
test('will output to specified directory', async () =>
13911428
withTemporaryDirectory(async (tmpDir) => {
13921429
// GIVEN

0 commit comments

Comments
 (0)