Skip to content

Commit

Permalink
fix: Parse all modifiers on a method to determine if it is public (an…
Browse files Browse the repository at this point in the history
…d hence exposed as an abi method)
  • Loading branch information
tristanmenzel committed Oct 10, 2024
1 parent e6b5ba5 commit 855d18a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 9 deletions.
66 changes: 60 additions & 6 deletions src/awst_build/contract-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,16 +204,21 @@ export class ContractVisitor extends BaseVisitor implements Visitor<ClassElement
private buildArc4Config({
decorator,
methodName,
isPublic,
modifiers: { isPublic, isStatic },
methodLocation,
}: {
methodName: string
decorator: DecoratorData | undefined
isPublic: boolean
modifiers: { isPublic: boolean; isStatic: boolean }
methodLocation: SourceLocation
}): awst.ContractMethod['arc4MethodConfig'] {
if (!isPublic && decorator && [Constants.arc4BareDecoratorName, Constants.arc4AbiDecoratorName].includes(decorator.type)) {
logger.error(methodLocation, 'Private method cannot be exposed as an abi method')
if (decorator && [Constants.arc4BareDecoratorName, Constants.arc4AbiDecoratorName].includes(decorator.type)) {
if (!isPublic) {
logger.error(methodLocation, 'Private or protected methods cannot be exposed as an abi method')
}
if (isStatic) {
logger.error(methodLocation, 'Static methods cannot be exposed as an abi method')
}
}

if (decorator?.type === 'arc4.baremethod') {
Expand Down Expand Up @@ -257,6 +262,54 @@ export class ContractVisitor extends BaseVisitor implements Visitor<ClassElement
return null
}

private parseMemberModifiers(node: { modifiers?: readonly ts.ModifierLike[] }) {
let isPublic = true
let isStatic = false
if (node.modifiers)
for (const m of node.modifiers) {
switch (m.kind) {
case ts.SyntaxKind.StaticKeyword:
isStatic = true
continue
case ts.SyntaxKind.PublicKeyword:
isPublic = true
continue
case ts.SyntaxKind.ProtectedKeyword:
isPublic = false
continue
case ts.SyntaxKind.PrivateKeyword:
isPublic = false
continue
case ts.SyntaxKind.AbstractKeyword:
// TODO: Do we need to do anything here?
continue
case ts.SyntaxKind.AccessorKeyword:
logger.error(this.sourceLocation(m), 'properties are not supported')
continue
case ts.SyntaxKind.AsyncKeyword:
logger.error(this.sourceLocation(m), 'async keyword is not supported')
continue
case ts.SyntaxKind.DeclareKeyword:
logger.error(this.sourceLocation(m), 'declare keyword is not supported')
continue
case ts.SyntaxKind.ExportKeyword:
case ts.SyntaxKind.ConstKeyword:
case ts.SyntaxKind.DefaultKeyword:
case ts.SyntaxKind.ReadonlyKeyword:
case ts.SyntaxKind.OverrideKeyword:
case ts.SyntaxKind.InKeyword:
case ts.SyntaxKind.OutKeyword:
case ts.SyntaxKind.Decorator:
// Ignore for now
continue
}
}
return {
isStatic,
isPublic,
}
}

visitMethodDeclaration(node: ts.MethodDeclaration): void {
const sourceLocation = this.sourceLocation(node)
const methodName = this.textVisitor.accept(node.name)
Expand All @@ -266,7 +319,8 @@ export class ContractVisitor extends BaseVisitor implements Visitor<ClassElement

return DecoratorVisitor.buildDecoratorData(this.context, modifier)
})
const isPublic = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.PublicKeyword) === true

const modifiers = this.parseMemberModifiers(node)

if (decorators.length > 1) {
logger.error(
Expand All @@ -291,7 +345,7 @@ export class ContractVisitor extends BaseVisitor implements Visitor<ClassElement
arc4MethodConfig: this.buildArc4Config({
decorator: decorators[0],
methodName,
isPublic,
modifiers,
methodLocation: sourceLocation,
}),
}),
Expand Down
4 changes: 4 additions & 0 deletions src/awst_build/ptypes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,10 @@ export const undefinedPType = new UnsupportedType({
module: 'lib.d.ts',
fullName: 'undefined',
})
export const promisePType = new UnsupportedType({
name: 'Promise',
module: 'typescript/lib/lib.es5.d.ts',
})
export const anyPType = new AnyPType()

export const boolPType = new InstanceType({
Expand Down
4 changes: 2 additions & 2 deletions src/awst_build/type-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
numberPType,
NumericLiteralPType,
ObjectPType,
promisePType,
StorageProxyPType,
StringFunction,
stringPType,
Expand Down Expand Up @@ -171,6 +172,7 @@ export class TypeResolver {
}

const typeName = this.getTypeName(tsType, sourceLocation)
if (typeName.fullName === promisePType.fullName) return promisePType
if (tsType.flags === ts.TypeFlags.TypeParameter) {
return new TypeParameterType(typeName)
}
Expand Down Expand Up @@ -284,8 +286,6 @@ export class TypeResolver {
properties[prop.name] = ptype
} else if (ptype instanceof FunctionPType) {
methods[prop.name] = ptype
} else {
throw new InternalError(`Unhandled property type ${ptype}`, { sourceLocation })
}
}
return new ContractClassPType({
Expand Down
2 changes: 1 addition & 1 deletion tests/expected-output/abi-decorators.algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default class AbiDecorators extends Contract {
// @expect-warn Duplicate on completion actions
@abimethod({ allowActions: ['NoOp', 'NoOp'] })
public justNoop(): void {}
// @expect-error Private method cannot be exposed as an abi method
// @expect-error Private or protected methods cannot be exposed as an abi method
@abimethod({ onCreate: 'require' })
private createMethod(): void {}
// @expect-error Only one decorator is allowed per method. Multiple on complete actions can be provided in a single decorator
Expand Down

0 comments on commit 855d18a

Please sign in to comment.