packages/awslint/lib/rules/imports.ts (104 lines of code) (raw):
import * as reflect from 'jsii-reflect';
import { AttributeSite, ResourceReflection } from './resource';
import { Linter } from '../linter';
export const importsLinter = new Linter<ImportsReflection>(assembly => ResourceReflection
.findAll(assembly)
.filter(r => r.construct && r.construct.interfaceType) // only resources that have an interface can have "fromXxx" methods
.map(construct => new ImportsReflection(construct)));
class ImportsReflection {
public readonly fromMethods: reflect.Method[];
public readonly prefix: string;
public readonly fromAttributesMethodName: string;
public readonly fromAttributesMethod?: reflect.Method;
public readonly attributesStructName: string;
public readonly attributesStruct?: reflect.InterfaceType;
constructor(public readonly resource: ResourceReflection) {
const sys = resource.sys;
this.prefix = `from${resource.basename}`;
const classType = resource.construct.classType;
this.fromAttributesMethodName = `${this.prefix}Attributes`;
this.fromAttributesMethod = classType.allMethods.find(x => x.name === this.fromAttributesMethodName);
this.fromMethods = classType.allMethods.filter(x =>
x.static
&& x.name.match(`^${this.prefix}[A-Z]`)
&& x.name !== this.fromAttributesMethodName);
const attributesStructFqn = `${classType.fqn}Attributes`;
const attributesStruct = sys.tryFindFqn(attributesStructFqn);
if (attributesStruct) {
if (!attributesStruct.isInterfaceType() || !attributesStruct.isDataType()) {
throw new Error(`Attributes type ${attributesStructFqn} must be an interface struct`);
}
}
this.attributesStructName = attributesStructFqn;
this.attributesStruct = attributesStruct;
}
}
importsLinter.add({
code: 'no-static-import',
message: 'static "import" methods are deprecated in favor of "fromAttributes" (see guidelines)',
eval: e => {
const hasImport = e.ctx.resource.construct.classType.allMethods.find(x => x.static && x.name === 'import');
e.assert(!hasImport, e.ctx.resource.fqn + '.import');
},
});
importsLinter.add({
code: 'from-method',
message: 'resource should have at least one "fromXxx" static method or "fromXxxAttributes"',
eval: e => {
// no attributes are defined on the interface, so we don't expect any "from" methods.
if (!e.ctx.resource.attributes.some(a => a.site === AttributeSite.Interface)) {
return;
}
e.assert(e.ctx.fromMethods.length > 0 || e.ctx.fromAttributesMethod, e.ctx.resource.fqn);
},
});
importsLinter.add({
code: 'from-signature',
message: 'invalid method signature for fromXxx method. ' + baseConstructAddendum(),
eval: e => {
for (const method of e.ctx.fromMethods) {
// "fromRoleArn" => "roleArn"
const argName = e.ctx.resource.basename[0].toLocaleLowerCase() + method.name.slice('from'.length + 1);
const baseType = e.ctx.resource.core.baseConstructClass;
e.assertSignature(method, {
parameters: [
{ name: 'scope', type: baseType },
{ name: 'id', type: 'string' },
{ name: argName, type: 'string' },
],
returns: e.ctx.resource.construct.interfaceType,
});
}
},
});
importsLinter.add({
code: 'from-attributes',
message: 'static fromXxxAttributes is a factory of IXxx from its primitive attributes. ' + baseConstructAddendum(),
eval: e => {
if (!e.ctx.fromAttributesMethod) {
return;
}
const baseType = e.ctx.resource.core.baseConstructClass;
e.assertSignature(e.ctx.fromAttributesMethod, {
parameters: [
{ name: 'scope', type: baseType },
{ name: 'id', type: 'string' },
{ name: 'attrs', type: e.ctx.attributesStruct },
],
});
},
});
importsLinter.add({
code: 'from-attributes-struct',
message: 'resource should have an XxxAttributes struct',
eval: e => {
if (!e.ctx.fromAttributesMethod) {
return; // no "fromAttributes" method
}
e.assert(e.ctx.attributesStruct, e.ctx.attributesStructName);
},
});
function baseConstructAddendum(): string {
if (!process.env.AWSLINT_BASE_CONSTRUCT) {
return 'If the construct is using the "constructs" module, set the environment variable "AWSLINT_BASE_CONSTRUCT" and re-run';
}
return '';
}