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(spec): model submodules in the Assembly schema #1563

Merged
merged 8 commits into from
Apr 20, 2020
14 changes: 13 additions & 1 deletion packages/@jsii/spec/lib/assembly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,19 @@ export interface Assembly extends AssemblyConfiguration, Documentable {
/**
* Shareable configuration of a jsii Assembly.
*/
export interface AssemblyConfiguration {
export interface AssemblyConfiguration extends Targetable {
/**
* Submodules declared in this assembly.
*
* @default none
*/
submodules?: { [fqn: string]: SourceLocatable & Targetable };
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is the readme contents going to be? Is it under Targetable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could be added to Targetable (moved there from Assembly) yeah...

I would do this when I actually start processing READMEs in a subsequent PR. I don't want to bloat this one :)

}

/**
* An entity on which targets may be configured.
*/
export interface Targetable {
/**
* A map of target name to configuration, which is used when generating
* packages for various languages.
Expand Down
58 changes: 57 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,62 @@
"url": "https://github.com/aws/jsii.git"
},
"schema": "jsii/0.10.0",
"submodules": {
"jsii-calc.DerivedClassHasNoProperties": {
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 309
}
},
"jsii-calc.InterfaceInNamespaceIncludesClasses": {
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1057
}
},
"jsii-calc.InterfaceInNamespaceOnlyInterface": {
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1048
}
},
"jsii-calc.composition": {
"locationInModule": {
"filename": "lib/calculator.ts",
"line": 127
}
},
"jsii-calc.submodule": {
"locationInModule": {
"filename": "lib/index.ts",
"line": 7
}
},
"jsii-calc.submodule.back_references": {
"locationInModule": {
"filename": "lib/submodule/index.ts",
"line": 4
}
},
"jsii-calc.submodule.child": {
"locationInModule": {
"filename": "lib/submodule/index.ts",
"line": 1
}
},
"jsii-calc.submodule.nested_submodule": {
"locationInModule": {
"filename": "lib/submodule/nested_submodule.ts",
"line": 3
}
},
"jsii-calc.submodule.nested_submodule.deeplyNested": {
"locationInModule": {
"filename": "lib/submodule/nested_submodule.ts",
"line": 4
}
}
},
"targets": {
"dotnet": {
"iconUrl": "https://sdk-for-net.amazonwebservices.com/images/AWSLogo128x128.png",
Expand Down Expand Up @@ -12442,5 +12498,5 @@
}
},
"version": "0.0.0",
"fingerprint": "XNjL7+hJ5KwFtBCVEz4TZH4c2JtVW/gTPS7UNrw02Cg="
"fingerprint": "LPI1XbecdL/VPbXVTmbAHHV0ox1k1cAxzrWF/f0rIxI="
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,62 @@
"url": "https://github.com/aws/jsii.git"
},
"schema": "jsii/0.10.0",
"submodules": {
"jsii-calc.DerivedClassHasNoProperties": {
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 309
}
},
"jsii-calc.InterfaceInNamespaceIncludesClasses": {
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1057
}
},
"jsii-calc.InterfaceInNamespaceOnlyInterface": {
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 1048
}
},
"jsii-calc.composition": {
"locationInModule": {
"filename": "lib/calculator.ts",
"line": 127
}
},
"jsii-calc.submodule": {
"locationInModule": {
"filename": "lib/index.ts",
"line": 7
}
},
"jsii-calc.submodule.back_references": {
"locationInModule": {
"filename": "lib/submodule/index.ts",
"line": 4
}
},
"jsii-calc.submodule.child": {
"locationInModule": {
"filename": "lib/submodule/index.ts",
"line": 1
}
},
"jsii-calc.submodule.nested_submodule": {
"locationInModule": {
"filename": "lib/submodule/nested_submodule.ts",
"line": 3
}
},
"jsii-calc.submodule.nested_submodule.deeplyNested": {
"locationInModule": {
"filename": "lib/submodule/nested_submodule.ts",
"line": 4
}
}
},
"targets": {
"dotnet": {
"iconUrl": "https://sdk-for-net.amazonwebservices.com/images/AWSLogo128x128.png",
Expand Down Expand Up @@ -12442,5 +12498,5 @@
}
},
"version": "0.0.0",
"fingerprint": "XNjL7+hJ5KwFtBCVEz4TZH4c2JtVW/gTPS7UNrw02Cg="
"fingerprint": "LPI1XbecdL/VPbXVTmbAHHV0ox1k1cAxzrWF/f0rIxI="
}
104 changes: 86 additions & 18 deletions packages/jsii/lib/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class Assembler implements Emitter {

/** Map of Symbol to namespace export Symbol */
private readonly _submoduleMap = new Map<ts.Symbol, ts.Symbol>();
private readonly _submodules = new Set<ts.Symbol>();
private readonly _submodules = new Map<ts.Symbol, SubmoduleSpec>();

/**
* @param projectInfo information about the package being assembled
Expand Down Expand Up @@ -153,6 +153,7 @@ export class Assembler implements Emitter {
dependencyClosure: noEmptyDict(toDependencyClosure(this.projectInfo.dependencyClosure)),
bundled: this.projectInfo.bundleDependencies,
types: this._types,
submodules: noEmptyDict(toSubmoduleDeclarations(this._submodules.values())),
targets: this.projectInfo.targets,
metadata: this.projectInfo.metadata,
docs,
Expand Down Expand Up @@ -345,17 +346,13 @@ export class Assembler implements Emitter {
return `unknown.${typeName}`;
}

let submodule = this._submoduleMap.get( type.symbol);
let submoduleNs = submodule?.name;
// Submodules can be in submodules themselves, so we crawl up the tree...
while (submodule != null && this._submoduleMap.has(submodule)) {
submodule = this._submoduleMap.get(submodule)!;
submoduleNs = `${submodule.name}.${submoduleNs}`;
const submodule = this._submoduleMap.get(type.symbol);
if (submodule != null) {
const submoduleNs = this._submodules.get(submodule)!.fqnResolutionPrefix;
return `${submoduleNs}.${typeName}`;
}

const fqn = submoduleNs != null
? `${pkg.name}.${submoduleNs}.${typeName}`
: `${pkg.name}.${typeName}`;
const fqn = `${pkg.name}.${typeName}`;
if (pkg.name !== this.projectInfo.name && !this._dereference({ fqn }, type.symbol.valueDeclaration)) {
this._diagnostic(node,
ts.DiagnosticCategory.Error,
Expand All @@ -376,10 +373,22 @@ export class Assembler implements Emitter {

private _registerNamespaces(symbol: ts.Symbol): void {
const declaration = symbol.valueDeclaration ?? symbol.declarations[0];
if (declaration == null || !ts.isNamespaceExport(declaration)) {
if (declaration == null) {
// Nothing to do here...
return;
}
if (ts.isModuleDeclaration(declaration)) {
const { fqn, fqnResolutionPrefix } = qualifiedNameOf.call(this, symbol, true);

this._submodules.set(symbol, { fqn, fqnResolutionPrefix, locationInModule: this.declarationLocation(declaration) });
this._addToSubmodule(symbol, symbol);
return;
}
if (!ts.isNamespaceExport(declaration)) {
// Nothing to do here...
return;
}

const moduleSpecifier = declaration.parent.moduleSpecifier;
if (moduleSpecifier == null || !ts.isStringLiteral(moduleSpecifier)) {
// There is a grammar error here, so we'll let tsc report this for us.
Expand Down Expand Up @@ -409,9 +418,29 @@ export class Assembler implements Emitter {
this._diagnostic(declaration, ts.DiagnosticCategory.Error,
`Submodule namespaces must be camelCased or snake_cased. Consider renaming to "${Case.camel(symbol.name)}".`);
}
this._submodules.add(symbol);

const { fqn, fqnResolutionPrefix } = qualifiedNameOf.call(this, symbol);
const targets = undefined; // This will be configurable in the future.

this._submodules.set(symbol, { fqn, fqnResolutionPrefix, targets, locationInModule: this.declarationLocation(declaration) });
this._addToSubmodule(symbol, sourceModule);
}

function qualifiedNameOf(this: Assembler, sym: ts.Symbol, inlineNamespace = false): { fqn: string, fqnResolutionPrefix: string } {
if (this._submoduleMap.has(sym)) {
const parent = this._submodules.get(this._submoduleMap.get(sym)!)!;
const fqn = `${parent.fqn}.${sym.name}`;
return {
fqn,
fqnResolutionPrefix: inlineNamespace ? parent.fqnResolutionPrefix : fqn,
};
}
const fqn = `${this.projectInfo.name}.${sym.name}`;
return {
fqn,
fqnResolutionPrefix: inlineNamespace ? this.projectInfo.name : fqn,
};
}
}

/**
Expand Down Expand Up @@ -479,9 +508,8 @@ export class Assembler implements Emitter {
this._addToSubmodule(ns, symbol);
}
} else if (ts.isModuleDeclaration(decl)) {
this._addToSubmodule(ns, symbol);
this._registerNamespaces(symbol);
} else if (ts.isNamespaceExport(decl)) {
this._submoduleMap.set(symbol, ns);
this._registerNamespaces(symbol);
}
}
Expand Down Expand Up @@ -548,7 +576,7 @@ export class Assembler implements Emitter {

// Let's quickly verify the declaration does not collide with a submodule. Submodules get case-adjusted for each
// target language separately, so names cannot collide with case-variations.
for (const submodule of this._submodules) {
for (const submodule of this._submodules.keys()) {
const candidates = Array.from(new Set([
submodule.name,
Case.camel(submodule.name),
Expand Down Expand Up @@ -1566,6 +1594,30 @@ export class Assembler implements Emitter {
}
}

interface SubmoduleSpec {
/**
* The submodule's fully qualified name.
*/
readonly fqn: string;

/**
* The submodule's fully qualified name prefix to use when resolving type FQNs. This does not
* include "inline namespace" names as those are already represented in the TypeCheckers' view of
* the type names.
*/
readonly fqnResolutionPrefix: string;

/**
* The location of the submodule definition in the source.
*/
readonly locationInModule: spec.SourceLocation;

/**
* Any customized configuration for the currentl submodule.
*/
readonly targets?: spec.AssemblyTargets;
}

function _fingerprint(assembly: spec.Assembly): spec.Assembly {
delete assembly.fingerprint;
assembly = sortJson(assembly);
Expand Down Expand Up @@ -1782,20 +1834,36 @@ function* intersect<T>(xs: Set<T>, ys: Set<T>) {
}
}

function noEmptyDict<T>(xs: {[key: string]: T}): {[key: string]: T} | undefined {
if (Object.keys(xs).length === 0) { return undefined; }
function noEmptyDict<T>(xs: Record<string, T> | undefined): Record<string, T> | undefined {
if (xs == null || Object.keys(xs).length === 0) { return undefined; }
return xs;
}

function toDependencyClosure(assemblies: readonly spec.Assembly[]): { [name: string]: spec.AssemblyConfiguration } {
const result: { [name: string]: spec.AssemblyTargets } = {};
for (const assembly of assemblies) {
if (!assembly.targets) { continue; }
result[assembly.name] = { targets: assembly.targets };
result[assembly.name] = {
submodules: assembly.submodules,
targets: assembly.targets,
};
}
return result;
}

function toSubmoduleDeclarations(submodules: IterableIterator<SubmoduleSpec>): spec.Assembly['submodules'] {
const result: spec.Assembly['submodules'] = {};

for (const submodule of submodules) {
result[submodule.fqn] = {
locationInModule: submodule.locationInModule,
targets: submodule.targets,
};
}

return result;
}

/**
* Check whether this type is the intrinsic TypeScript "error type"
*
Expand Down