Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rosetta): transliterate loose mode #2892

Merged
merged 6 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions packages/jsii-pacmak/lib/targets/go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,11 @@ export class Golang extends Target {
// local build directory for this module. if
// we do, add a "replace" directive to point to it instead of download from
// the network.
const visit = (pkg: RootPackage) => {
const visit = async (pkg: RootPackage) => {
for (const dep of pkg.packageDependencies) {
for (const baseDir of dirs) {
const moduleDir = tryFindLocalModule(baseDir, dep);
// eslint-disable-next-line no-await-in-loop
const moduleDir = await tryFindLocalModule(baseDir, dep);
if (moduleDir) {
replace[dep.goModuleName] = moduleDir;

Expand All @@ -107,11 +108,12 @@ export class Golang extends Target {
}

// recurse to transitive deps ("replace" is only considered at the top level go.mod)
visit(dep);
// eslint-disable-next-line no-await-in-loop
await visit(dep);
}
};

visit(this.goGenerator.rootPackage);
await visit(this.goGenerator.rootPackage);

// write `local.go.mod`

Expand Down Expand Up @@ -198,14 +200,14 @@ class GoGenerator implements IGenerator {
* @param baseDir the `dist/go` directory
* @returns `undefined` if not or the module directory otherwise.
*/
function tryFindLocalModule(baseDir: string, pkg: RootPackage) {
async function tryFindLocalModule(baseDir: string, pkg: RootPackage) {
const gomodPath = path.join(baseDir, pkg.packageName, GOMOD_FILENAME);
if (!fs.pathExistsSync(gomodPath)) {
if (!(await fs.pathExists(gomodPath))) {
return undefined;
}

// read `go.mod` and check that it is for the correct module
const gomod = fs.readFileSync(gomodPath, 'utf-8').split('\n');
const gomod = (await fs.readFile(gomodPath, 'utf-8')).split('\n');
const isExpectedModule = gomod.find(
(line) => line.trim() === `module ${pkg.goModuleName}`,
);
Expand Down
10 changes: 9 additions & 1 deletion packages/jsii-rosetta/bin/jsii-rosetta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,17 @@ function main() {
})
.options('strict', {
alias: 's',
type: 'boolean',
conflicts: 'loose',
describe:
'Fail if an example that needs live transliteration fails to compile (which could cause incorrect transpilation results)',
type: 'boolean',
})
.options('loose', {
alias: 'l',
conflicts: 'strict',
describe:
'Ignore missing fixtures and literate markdown files instead of failing',
type: 'boolean',
})
.option('tablet', {
alias: 't',
Expand Down
3 changes: 2 additions & 1 deletion packages/jsii-rosetta/lib/commands/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface ExtractOptions {
export async function extractSnippets(
assemblyLocations: string[],
options: ExtractOptions,
loose = false,
): Promise<ExtractResult> {
const only = options.only ?? [];

Expand All @@ -37,7 +38,7 @@ export async function extractSnippets(
options.validateAssemblies,
);

let snippets = allTypeScriptSnippets(assemblies);
let snippets = allTypeScriptSnippets(assemblies, loose);
if (only.length > 0) {
snippets = filterSnippets(snippets, only);
}
Expand Down
15 changes: 13 additions & 2 deletions packages/jsii-rosetta/lib/commands/transliterate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@ import { Translation } from '../tablets/tablets';

export interface TransliterateAssemblyOptions {
/**
* Whather transliteration should fail upon failing to compile an example that
* Whether to ignore any missing fixture files or literate markdown documents
* referenced by the assembly, instead of failing.
*
* @default false
*/
readonly loose?: boolean;

/**
* Whether transliteration should fail upon failing to compile an example that
* required live transliteration.
*
* @default false
Expand Down Expand Up @@ -46,6 +54,7 @@ export async function transliterateAssembly(
const rosetta = new Rosetta({
includeCompilerDiagnostics: true,
liveConversion: true,
loose: options.loose,
targetLanguages,
});
if (options.tablet) {
Expand All @@ -71,7 +80,7 @@ export async function transliterateAssembly(
);
}
for (const type of Object.values(result.types ?? {})) {
transliterateType(type, rosetta, language, location);
transliterateType(type, rosetta, language, location, options.loose);
}
// eslint-disable-next-line no-await-in-loop
await writeJson(
Expand Down Expand Up @@ -153,6 +162,7 @@ function transliterateType(
rosetta: Rosetta,
language: TargetLanguage,
workingDirectory: string,
loose = false,
): void {
transliterateDocs(type.docs);
switch (type.kind) {
Expand Down Expand Up @@ -193,6 +203,7 @@ function transliterateType(
true /* strict */,
{ [SnippetParameters.$PROJECT_DIRECTORY]: workingDirectory },
),
loose,
);
const translation = rosetta.translateSnippet(snippet, language);
if (translation != null) {
Expand Down
25 changes: 20 additions & 5 deletions packages/jsii-rosetta/lib/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { TypeScriptSnippet, SnippetParameters } from './snippet';
/**
* Complete snippets with fixtures, if required
*/
export function fixturize(snippet: TypeScriptSnippet): TypeScriptSnippet {
export function fixturize(
snippet: TypeScriptSnippet,
loose = false,
): TypeScriptSnippet {
let source = snippet.visibleSource;
const parameters = snippet.parameters ?? {};

Expand All @@ -25,14 +28,14 @@ export function fixturize(snippet: TypeScriptSnippet): TypeScriptSnippet {
if (literateSource) {
// Compatibility with the "old school" example inclusion mechanism.
// Completely load this file and attach a parameter with its directory.
source = loadLiterateSource(directory, literateSource);
source = loadLiterateSource(directory, literateSource, loose);
parameters[SnippetParameters.$COMPILATION_DIRECTORY] = path.join(
directory,
path.dirname(literateSource),
);
} else if (parameters[SnippetParameters.FIXTURE]) {
// Explicitly request a fixture
source = loadAndSubFixture(directory, parameters.fixture, source, true);
// Explicitly requested fixture must exist, unless we are operating in loose mode
source = loadAndSubFixture(directory, parameters.fixture, source, !loose);
} else if (parameters[SnippetParameters.NO_FIXTURE] === undefined) {
// Don't explicitly request no fixture
source = loadAndSubFixture(directory, 'default', source, false);
Expand All @@ -45,10 +48,22 @@ export function fixturize(snippet: TypeScriptSnippet): TypeScriptSnippet {
};
}

function loadLiterateSource(directory: string, literateFileName: string) {
function loadLiterateSource(
directory: string,
literateFileName: string,
loose = false,
) {
const fullPath = path.join(directory, literateFileName);
const exists = fs.existsSync(fullPath);
if (!exists) {
if (loose) {
// In loose mode, we'll fall back to the `.js` file if it exists...
const jsFile = fullPath.replace(/\.ts(x?)$/, '.js$1');
if (fs.existsSync(jsFile)) {
return fs.readFileSync(jsFile, { encoding: 'utf-8' });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a header here saying we are showing js code because the expected file is missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is unfortunately no way to reliably do this... Leading comments will be ignored if they are not attached to a render-able statement, which may not even exist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh well, we tried

}
return `Missing literate source file ${literateFileName}`;
}
// This couldn't really happen in practice, but do the check anyway
throw new Error(
`Sample uses literate source ${literateFileName}, but not found: ${fullPath}`,
Expand Down
5 changes: 3 additions & 2 deletions packages/jsii-rosetta/lib/jsii/assemblies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function removeSlashes(x: string) {

export function* allTypeScriptSnippets(
assemblies: readonly LoadedAssembly[],
loose = false,
): IterableIterator<TypeScriptSnippet> {
for (const { assembly, directory } of assemblies) {
const strict = enforcesStrictMode(assembly);
Expand All @@ -146,7 +147,7 @@ export function* allTypeScriptSnippets(
[SnippetParameters.$PROJECT_DIRECTORY]: directory,
},
);
yield fixturize(snippet);
yield fixturize(snippet, loose);
break;
case 'markdown':
for (const snippet of extractTypescriptSnippetsFromMarkdown(
Expand All @@ -157,7 +158,7 @@ export function* allTypeScriptSnippets(
const withDirectory = updateParameters(snippet, {
[SnippetParameters.$PROJECT_DIRECTORY]: directory,
});
yield fixturize(withDirectory);
yield fixturize(withDirectory, loose);
}
}
}
Expand Down
17 changes: 14 additions & 3 deletions packages/jsii-rosetta/lib/rosetta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export interface RosettaOptions {
* Whether to include compiler diagnostics in the compilation results.
*/
readonly includeCompilerDiagnostics?: boolean;

/**
* Whether this Rosetta should operate in "loose" mode, where missing literate
* source files and missing fixtures are ignored instead of failing.
*
* @default false
*/
readonly loose?: boolean;
}

/**
Expand All @@ -60,8 +68,10 @@ export class Rosetta {
private readonly liveTablet = new LanguageTablet();
private readonly extractedSnippets = new Map<string, TypeScriptSnippet>();
private readonly translator: Translator;
private readonly loose: boolean;

public constructor(private readonly options: RosettaOptions = {}) {
this.loose = !!options.loose;
this.translator = new Translator(
options.includeCompilerDiagnostics ?? false,
);
Expand Down Expand Up @@ -114,9 +124,10 @@ export class Rosetta {
}

if (this.options.liveConversion) {
for (const tsnip of allTypeScriptSnippets([
{ assembly, directory: assemblyDir },
])) {
for (const tsnip of allTypeScriptSnippets(
[{ assembly, directory: assemblyDir }],
this.loose,
)) {
this.extractedSnippets.set(tsnip.visibleSource, tsnip);
}
}
Expand Down
Loading