Skip to content

Commit 30eded9

Browse files
authored
fix(rosetta): tablet compression handled incorrectly in multiple places (#3670)
Unfortunately, #3652 was half-baked and this PR serves to finish handling tablet compression. It introduces the following: - `compress-cache` cli option so that you can use a compressed cache file - update to all usages of `languageTablet.save()` to explicitly pass the compress flag - `languageTablet.compressedSource`, which is set when we load from a compressed source, so that we can remember to save as a compressed source. - `loadAllDefaultTablets` now handles compressed default tablets. - a far more encompassing set of unit tests for the above features --- 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 a5bd219 commit 30eded9

File tree

9 files changed

+139
-16
lines changed

9 files changed

+139
-16
lines changed

packages/jsii-rosetta/bin/jsii-rosetta.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,12 @@ function main() {
231231
.options('compress-tablet', {
232232
alias: 'z',
233233
type: 'boolean',
234-
describe: 'Compress the resulting tablet file',
234+
describe: 'Compress the implicit tablet file',
235+
default: false,
236+
})
237+
.options('compress-cache', {
238+
type: 'boolean',
239+
describe: 'Compress the cache-to file',
235240
default: false,
236241
})
237242
.conflicts('loose', 'strict')
@@ -259,6 +264,7 @@ function main() {
259264
trimCache: args['trim-cache'],
260265
loose: args.loose,
261266
compressTablet: args['compress-tablet'],
267+
compressCacheToFile: args['compress-cache'],
262268
};
263269

264270
const result = args.infuse

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,26 @@ export interface ExtractOptions {
7575
readonly allowDirtyTranslations?: boolean;
7676

7777
/**
78-
* Compress the resulting tablet file
78+
* Compress the implicit tablet files
7979
*
8080
* @default false
8181
*/
8282
readonly compressTablet?: boolean;
83+
84+
/**
85+
* Compress the cacheToFile tablet.
86+
*
87+
* @default false
88+
*/
89+
readonly compressCacheToFile?: boolean;
8390
}
8491

8592
export async function extractAndInfuse(assemblyLocations: string[], options: ExtractOptions): Promise<ExtractResult> {
8693
const result = await extractSnippets(assemblyLocations, options);
8794
await infuse(assemblyLocations, {
8895
cacheFromFile: options.cacheFromFile,
8996
cacheToFile: options.cacheToFile,
97+
compressCacheToFile: options.compressCacheToFile,
9098
});
9199
return result;
92100
}
@@ -157,7 +165,7 @@ export async function extractSnippets(
157165
logging.info('Nothing left to translate');
158166
}
159167

160-
// Save to individual tablet files, and optionally append to the output file
168+
// Save to individual tablet files
161169
if (options.writeToImplicitTablets ?? true) {
162170
await Promise.all(
163171
Object.entries(snippetsPerAssembly).map(async ([location, snips]) => {
@@ -175,13 +183,14 @@ export async function extractSnippets(
175183
);
176184
}
177185

186+
// optionally append to the output file
178187
if (options.cacheToFile) {
179188
logging.info(`Adding translations to ${options.cacheToFile}`);
180189
const output = options.trimCache
181190
? new LanguageTablet()
182191
: await LanguageTablet.fromOptionalFile(options.cacheToFile);
183192
output.addTablets(translator.tablet);
184-
await output.save(options.cacheToFile);
193+
await output.save(options.cacheToFile, options.compressCacheToFile);
185194
}
186195

187196
return { diagnostics, tablet: translator.tablet };

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import {
1212
import { renderMetadataline, TypeScriptSnippet } from '../snippet';
1313
import { SnippetSelector, mean, meanLength, shortest, longest } from '../snippet-selectors';
1414
import { snippetKey } from '../tablets/key';
15-
import { LanguageTablet, TranslatedSnippet, DEFAULT_TABLET_NAME } from '../tablets/tablets';
15+
import {
16+
LanguageTablet,
17+
TranslatedSnippet,
18+
DEFAULT_TABLET_NAME,
19+
DEFAULT_TABLET_NAME_COMPRESSED,
20+
} from '../tablets/tablets';
1621
import { isDefined, mkDict, indexBy } from '../util';
1722

1823
export interface InfuseResult {
@@ -36,6 +41,11 @@ export interface InfuseOptions {
3641
* In addition to the implicit tablets, also write all added examples to this additional output tablet
3742
*/
3843
readonly cacheToFile?: string;
44+
45+
/**
46+
* Compress the cacheToFile
47+
*/
48+
readonly compressCacheToFile?: boolean;
3949
}
4050

4151
export const DEFAULT_INFUSION_RESULTS_NAME = 'infusion-results.html';
@@ -87,6 +97,10 @@ export async function infuse(assemblyLocations: string[], options?: InfuseOption
8797
stream?.write(`<h1>${assembly.name}</h1>\n`);
8898

8999
const implicitTablet = defaultTablets[directory];
100+
const implicitTabletFile = path.join(
101+
directory,
102+
implicitTablet.compressedSource ? DEFAULT_TABLET_NAME_COMPRESSED : DEFAULT_TABLET_NAME,
103+
);
90104
if (!implicitTablet) {
91105
throw new Error(`No tablet found for ${directory}`);
92106
}
@@ -110,7 +124,7 @@ export async function infuse(assemblyLocations: string[], options?: InfuseOption
110124
// eslint-disable-next-line no-await-in-loop
111125
await Promise.all([
112126
replaceAssembly(assembly, directory),
113-
implicitTablet.save(path.join(directory, DEFAULT_TABLET_NAME)),
127+
implicitTablet.save(implicitTabletFile, implicitTablet.compressedSource),
114128
]);
115129
}
116130

@@ -130,7 +144,7 @@ export async function infuse(assemblyLocations: string[], options?: InfuseOption
130144
// If we copied examples onto different types, we'll also have inserted new snippets
131145
// with different keys into the tablet. We must now write the updated tablet somewhere.
132146
if (options?.cacheToFile) {
133-
await additionalOutputTablet.save(options.cacheToFile);
147+
await additionalOutputTablet.save(options.cacheToFile, options.compressCacheToFile);
134148
}
135149

136150
return {

packages/jsii-rosetta/lib/commands/trim-cache.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export async function trimCache(options: TrimCacheOptions): Promise<void> {
2525
const original = await LanguageTablet.fromFile(options.cacheFile);
2626
const updated = new LanguageTablet();
2727
updated.addSnippets(...snippets.map((snip) => original.tryGetSnippet(snippetKey(snip))).filter(isDefined));
28-
await updated.save(options.cacheFile);
28+
// if the original file was compressed, then compress the updated file too
29+
await updated.save(options.cacheFile, original.compressedSource);
2930

3031
// eslint-disable-next-line prettier/prettier
3132
logging.info(`${options.cacheFile}: ${updated.count} snippets remaining (${original.count} - ${updated.count} trimmed)`);

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
INITIALIZER_METHOD_NAME,
2525
} from '../snippet';
2626
import { enforcesStrictMode } from '../strict';
27-
import { LanguageTablet, DEFAULT_TABLET_NAME } from '../tablets/tablets';
27+
import { LanguageTablet, DEFAULT_TABLET_NAME, DEFAULT_TABLET_NAME_COMPRESSED } from '../tablets/tablets';
2828
import { fmap, mkDict, sortBy } from '../util';
2929

3030
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
@@ -92,12 +92,15 @@ export function loadAssemblies(
9292
export async function loadAllDefaultTablets(asms: readonly LoadedAssembly[]): Promise<Record<string, LanguageTablet>> {
9393
return mkDict(
9494
await Promise.all(
95-
asms.map(
96-
async (a) =>
97-
[a.directory, await LanguageTablet.fromOptionalFile(path.join(a.directory, DEFAULT_TABLET_NAME))] as const,
98-
),
95+
asms.map(async (a) => [a.directory, await LanguageTablet.fromOptionalFile(guessTabletLocation(a))] as const),
9996
),
10097
);
98+
99+
function guessTabletLocation(a: LoadedAssembly): string {
100+
const defaultTablet = path.join(a.directory, DEFAULT_TABLET_NAME);
101+
const compDefaultTablet = path.join(a.directory, DEFAULT_TABLET_NAME_COMPRESSED);
102+
return fs.existsSync(defaultTablet) ? defaultTablet : compDefaultTablet;
103+
}
101104
}
102105

103106
export type AssemblySnippetSource =

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export class LanguageTablet {
5454
return ret;
5555
}
5656

57+
/**
58+
* Whether or not the LanguageTablet was loaded with a compressed source.
59+
* This gets used to determine if it should be compressed when saved.
60+
*/
61+
public compressedSource = false;
62+
5763
private readonly snippets: Record<string, TranslatedSnippet> = {};
5864

5965
/**
@@ -140,6 +146,7 @@ export class LanguageTablet {
140146
if (data[0] === 0x1f && data[1] === 0x8b && data[2] === 0x08) {
141147
// This is a gz object, so we decompress it now...
142148
data = zlib.gunzipSync(data);
149+
this.compressedSource = true;
143150
}
144151

145152
const obj: TabletSchema = JSON.parse(data.toString('utf-8'));

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DEFAULT_TABLET_NAME,
1111
TranslatedSnippet,
1212
typeScriptSnippetFromVisibleSource,
13+
DEFAULT_TABLET_NAME_COMPRESSED,
1314
} from '../../lib';
1415
import * as extract from '../../lib/commands/extract';
1516
import { loadAssemblies } from '../../lib/jsii/assemblies';
@@ -71,19 +72,37 @@ test('extract samples from test assembly', async () => {
7172
await tablet.load(cacheToFile);
7273

7374
expect(tablet.snippetKeys.length).toEqual(1);
75+
expect(tablet.compressedSource).toBeFalsy();
7476
});
7577

76-
test('extract can save/load compressed tablets', async () => {
78+
test('extract can compress cached tablet file', async () => {
7779
const compressedCacheFile = path.join(assembly.moduleDirectory, 'test.tabl.gz');
7880
await extract.extractSnippets([assembly.moduleDirectory], {
7981
cacheToFile: compressedCacheFile,
82+
compressCacheToFile: true,
8083
...defaultExtractOptions,
8184
});
8285

8386
const tablet = new LanguageTablet();
8487
await tablet.load(compressedCacheFile);
8588

8689
expect(tablet.snippetKeys.length).toEqual(1);
90+
expect(tablet.compressedSource).toBeTruthy();
91+
});
92+
93+
test('extract can compress implicit tablet file', async () => {
94+
await extract.extractSnippets([assembly.moduleDirectory], {
95+
...defaultExtractOptions,
96+
compressTablet: true,
97+
});
98+
99+
const compImplicitTablet = path.join(assembly.moduleDirectory, DEFAULT_TABLET_NAME_COMPRESSED);
100+
expect(fs.existsSync(compImplicitTablet)).toBeTruthy();
101+
expect(fs.existsSync(path.join(assembly.moduleDirectory, DEFAULT_TABLET_NAME))).toBeFalsy();
102+
103+
const tablet = new LanguageTablet();
104+
await tablet.load(compImplicitTablet);
105+
expect(tablet.snippetKeys.length).toEqual(1);
87106
});
88107

89108
test('extract works from compressed test assembly', async () => {

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { loadAssemblyFromPath, SPEC_FILE_NAME, SPEC_FILE_NAME_COMPRESSED } from
22
import * as fs from 'fs-extra';
33
import * as path from 'path';
44

5-
import { LanguageTablet, DEFAULT_TABLET_NAME } from '../../lib';
5+
import { LanguageTablet, DEFAULT_TABLET_NAME, DEFAULT_TABLET_NAME_COMPRESSED } from '../../lib';
66
import { extractSnippets } from '../../lib/commands/extract';
77
import { infuse, DEFAULT_INFUSION_RESULTS_NAME } from '../../lib/commands/infuse';
88
import { loadAssemblies } from '../../lib/jsii/assemblies';
@@ -162,3 +162,41 @@ test('preserves the assembly compression if present', async () => {
162162
expect(types).toBeDefined();
163163
expect(types!['my_assembly.ClassA'].docs?.example).toBeDefined();
164164
});
165+
166+
test('can infuse with compressed default tablets', async () => {
167+
// remove any tablets that may currently exist
168+
const implicitTablet = path.join(assembly.moduleDirectory, DEFAULT_TABLET_NAME);
169+
const compImplicitTablet = path.join(assembly.moduleDirectory, DEFAULT_TABLET_NAME_COMPRESSED);
170+
await fs.remove(implicitTablet);
171+
await fs.remove(compImplicitTablet);
172+
173+
// create a compressed implicit tablet file via extract
174+
await extractSnippets([assembly.moduleDirectory], {
175+
includeCompilerDiagnostics: false,
176+
validateAssemblies: false,
177+
compressTablet: true,
178+
});
179+
180+
expect(fs.existsSync(compImplicitTablet)).toBeTruthy();
181+
expect(fs.existsSync(implicitTablet)).toBeFalsy();
182+
183+
// infuse can use compressed implicit tablets
184+
await infuse([assembly.moduleDirectory]);
185+
186+
const assemblies = loadAssemblies([assembly.moduleDirectory], false);
187+
const types = assemblies[0].assembly.types;
188+
expect(types).toBeDefined();
189+
expect(types!['my_assembly.ClassA'].docs?.example).toBeDefined();
190+
});
191+
192+
test('can compress cacheToFile', async () => {
193+
const compressedTabletFile = path.join(assembly.moduleDirectory, 'tabl.json.gz');
194+
195+
await infuse([assembly.moduleDirectory], {
196+
cacheToFile: compressedTabletFile,
197+
compressCacheToFile: true,
198+
});
199+
200+
const tablet = await LanguageTablet.fromFile(compressedTabletFile);
201+
expect(tablet.compressedSource).toBeTruthy();
202+
});

packages/jsii-rosetta/test/commands/trim-cache.test.ts

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

3-
import { TranslatedSnippet, typeScriptSnippetFromVisibleSource, LanguageTablet, DEFAULT_TABLET_NAME } from '../../lib';
3+
import {
4+
TranslatedSnippet,
5+
typeScriptSnippetFromVisibleSource,
6+
LanguageTablet,
7+
DEFAULT_TABLET_NAME,
8+
DEFAULT_TABLET_NAME_COMPRESSED,
9+
} from '../../lib';
410
import { extractSnippets } from '../../lib/commands/extract';
511
import { trimCache } from '../../lib/commands/trim-cache';
612
import { TestJsiiModule, DUMMY_JSII_CONFIG, testSnippetLocation } from '../testutil';
@@ -77,6 +83,26 @@ test('trim-cache leaves used snippets', async () => {
7783
expect(updated.count).toEqual(1);
7884
});
7985

86+
test('trim-cache preserves tablet compression', async () => {
87+
const compDefaultTablet = path.join(assembly.moduleDirectory, DEFAULT_TABLET_NAME_COMPRESSED);
88+
89+
// GIVEN
90+
const tbl = new LanguageTablet();
91+
tbl.addSnippets(bogusTranslatedSnippet());
92+
// explicitly compress the tablet file
93+
await tbl.save(compDefaultTablet, true);
94+
95+
// WHEN
96+
await trimCache({
97+
assemblyLocations: [assembly.moduleDirectory],
98+
cacheFile: compDefaultTablet,
99+
});
100+
101+
// THEN
102+
const updated = await LanguageTablet.fromFile(compDefaultTablet);
103+
expect(updated.compressedSource).toBeTruthy();
104+
});
105+
80106
function bogusTranslatedSnippet() {
81107
return TranslatedSnippet.fromTypeScript(
82108
typeScriptSnippetFromVisibleSource('console.log("hello");', testSnippetLocation('x.ts'), true),

0 commit comments

Comments
 (0)