Skip to content

Commit d0e8e6d

Browse files
committed
support for ContractSpecifiers
1 parent fe13bda commit d0e8e6d

7 files changed

+203
-19
lines changed

src/slang-comments/handlers/handle-contract-definition-comments.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ export default function handleContractDefinitionComments({
2323
locEnd(comment)
2424
);
2525

26-
// Everything before the InheritanceSpecifier is pushed onto the beginning of
26+
// Everything before the ContractSpecifiers is pushed onto the beginning of
2727
// the ContractDefinition.
2828
if (
29-
followingNode?.kind === NonterminalKind.InheritanceSpecifier ||
29+
followingNode?.kind === NonterminalKind.ContractSpecifiers ||
3030
(followingNode?.kind === NonterminalKind.ContractMembers &&
3131
nextCharacter !== '{')
3232
) {
@@ -42,16 +42,25 @@ export default function handleContractDefinitionComments({
4242

4343
// The last comments before the body.
4444
if (nextCharacter === '{') {
45-
// If there's an InheritanceSpecifier, the comment is appended to the last
46-
// InheritanceType.
47-
if (
48-
precedingNode?.kind === NonterminalKind.InheritanceSpecifier &&
49-
precedingNode.types.items.length > 0
50-
) {
51-
addTrailingComment(
52-
precedingNode.types.items[precedingNode.types.items.length - 1],
53-
comment
54-
);
45+
if (precedingNode?.kind === NonterminalKind.ContractSpecifiers) {
46+
if (precedingNode.items.length === 0) {
47+
addTrailingComment(precedingNode, comment);
48+
return true;
49+
}
50+
const lastContractSpecifier =
51+
precedingNode.items[precedingNode.items.length - 1].variant;
52+
// If the last ContractSpecifier's an InheritanceSpecifier, the comment
53+
// is appended to the last InheritanceType.
54+
if (lastContractSpecifier.kind === NonterminalKind.InheritanceSpecifier) {
55+
addTrailingComment(
56+
lastContractSpecifier.types.items[
57+
lastContractSpecifier.types.items.length - 1
58+
],
59+
comment
60+
);
61+
return true;
62+
}
63+
addTrailingComment(lastContractSpecifier, comment);
5564
return true;
5665
}
5766
}

src/slang-nodes/ContractDefinition.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { coerce, satisfies } from 'semver';
33
import { NonterminalKind } from '@nomicfoundation/slang/cst';
44
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
55
import { Identifier } from './Identifier.js';
6-
import { InheritanceSpecifier } from './InheritanceSpecifier.js';
6+
import { ContractSpecifiers } from './ContractSpecifiers.js';
77
import { ContractMembers } from './ContractMembers.js';
88

99
import type * as ast from '@nomicfoundation/slang/ast';
@@ -24,7 +24,7 @@ export class ContractDefinition implements SlangNode {
2424

2525
name: Identifier;
2626

27-
inheritance?: InheritanceSpecifier;
27+
specifiers: ContractSpecifiers;
2828

2929
members: ContractMembers;
3030

@@ -33,12 +33,10 @@ export class ContractDefinition implements SlangNode {
3333

3434
this.abstractKeyword = ast.abstractKeyword?.unparse();
3535
this.name = new Identifier(ast.name);
36-
if (ast.inheritance) {
37-
this.inheritance = new InheritanceSpecifier(ast.inheritance, options);
38-
}
36+
this.specifiers = new ContractSpecifiers(ast.specifiers, options);
3937
this.members = new ContractMembers(ast.members, options);
4038

41-
metadata = updateMetadata(metadata, [this.inheritance, this.members]);
39+
metadata = updateMetadata(metadata, [this.specifiers, this.members]);
4240

4341
this.comments = metadata.comments;
4442
this.loc = metadata.loc;
@@ -68,7 +66,8 @@ export class ContractDefinition implements SlangNode {
6866
this.abstractKeyword ? 'abstract ' : '',
6967
'contract ',
7068
path.call(print, 'name'),
71-
this.inheritance ? [' ', path.call(print, 'inheritance')] : line,
69+
path.call(print, 'specifiers'),
70+
this.specifiers.items.length > 0 ? '' : line,
7271
'{'
7372
]),
7473
path.call(print, 'members'),

src/slang-nodes/ContractSpecifier.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { NonterminalKind } from '@nomicfoundation/slang/cst';
2+
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
3+
import { InheritanceSpecifier } from './InheritanceSpecifier.js';
4+
import { StorageLayoutSpecifier } from './StorageLayoutSpecifier.js';
5+
6+
import type * as ast from '@nomicfoundation/slang/ast';
7+
import type { AstPath, Doc, ParserOptions } from 'prettier';
8+
import type { AstNode } from './types.js';
9+
import type { PrintFunction, SlangNode } from '../types.js';
10+
11+
export class ContractSpecifier implements SlangNode {
12+
readonly kind = NonterminalKind.ContractSpecifier;
13+
14+
comments;
15+
16+
loc;
17+
18+
variant: InheritanceSpecifier | StorageLayoutSpecifier;
19+
20+
constructor(ast: ast.ContractSpecifier, options: ParserOptions<AstNode>) {
21+
let metadata = getNodeMetadata(ast);
22+
23+
switch (ast.variant.cst.kind) {
24+
case NonterminalKind.InheritanceSpecifier:
25+
this.variant = new InheritanceSpecifier(
26+
ast.variant as ast.InheritanceSpecifier,
27+
options
28+
);
29+
break;
30+
case NonterminalKind.StorageLayoutSpecifier:
31+
this.variant = new StorageLayoutSpecifier(
32+
ast.variant as ast.StorageLayoutSpecifier,
33+
options
34+
);
35+
break;
36+
default:
37+
throw new Error(`Unexpected variant: ${ast.variant.cst.kind}`);
38+
}
39+
metadata = updateMetadata(metadata, [this.variant]);
40+
41+
this.comments = metadata.comments;
42+
this.loc = metadata.loc;
43+
}
44+
45+
print(path: AstPath<ContractSpecifier>, print: PrintFunction): Doc {
46+
return path.call(print, 'variant');
47+
}
48+
}

src/slang-nodes/ContractSpecifiers.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { doc } from 'prettier';
2+
import { NonterminalKind } from '@nomicfoundation/slang/cst';
3+
import { sortContractSpecifiers } from '../slang-utils/sort-contract-specifiers.js';
4+
import { printSeparatedList } from '../slang-printers/print-separated-list.js';
5+
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
6+
import { ContractSpecifier } from './ContractSpecifier.js';
7+
8+
import type * as ast from '@nomicfoundation/slang/ast';
9+
import type { AstPath, Doc, ParserOptions } from 'prettier';
10+
import type { AstNode } from './types.js';
11+
import type { PrintFunction, SlangNode } from '../types.js';
12+
13+
const { group, ifBreak, line, softline } = doc.builders;
14+
15+
export class ContractSpecifiers implements SlangNode {
16+
readonly kind = NonterminalKind.ContractSpecifiers;
17+
18+
comments;
19+
20+
loc;
21+
22+
items: ContractSpecifier[];
23+
24+
constructor(ast: ast.ContractSpecifiers, options: ParserOptions<AstNode>) {
25+
let metadata = getNodeMetadata(ast, true);
26+
27+
this.items = ast.items.map((item) => new ContractSpecifier(item, options));
28+
29+
metadata = updateMetadata(metadata, [this.items]);
30+
31+
this.comments = metadata.comments;
32+
this.loc = metadata.loc;
33+
34+
this.items = this.items.sort(sortContractSpecifiers);
35+
}
36+
37+
print(path: AstPath<ContractSpecifiers>, print: PrintFunction): Doc {
38+
if (this.items.length === 0) return '';
39+
40+
const [specifier1, specifier2] = path.map(print, 'items');
41+
if (typeof specifier2 === 'undefined') return [' ', specifier1];
42+
43+
const groupId = Symbol('Slang.ContractSpecifiers.inheritance');
44+
return printSeparatedList(
45+
[group(specifier1, { id: groupId }), specifier2],
46+
{
47+
firstSeparator: line,
48+
separator: ifBreak('', softline, { groupId })
49+
}
50+
);
51+
}
52+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { doc } from 'prettier';
2+
import { NonterminalKind } from '@nomicfoundation/slang/cst';
3+
import { printSeparatedItem } from '../slang-printers/print-separated-item.js';
4+
import { getNodeMetadata, updateMetadata } from '../slang-utils/metadata.js';
5+
import { Expression } from './Expression.js';
6+
7+
import type * as ast from '@nomicfoundation/slang/ast';
8+
import type { AstPath, Doc, ParserOptions } from 'prettier';
9+
import type { AstNode } from './types.js';
10+
import type { PrintFunction, SlangNode } from '../types.js';
11+
12+
const { line } = doc.builders;
13+
14+
export class StorageLayoutSpecifier implements SlangNode {
15+
readonly kind = NonterminalKind.StorageLayoutSpecifier;
16+
17+
comments;
18+
19+
loc;
20+
21+
expression: Expression;
22+
23+
constructor(
24+
ast: ast.StorageLayoutSpecifier,
25+
options: ParserOptions<AstNode>
26+
) {
27+
let metadata = getNodeMetadata(ast);
28+
29+
this.expression = new Expression(ast.expression, options);
30+
31+
metadata = updateMetadata(metadata, [this.expression]);
32+
33+
this.comments = metadata.comments;
34+
this.loc = metadata.loc;
35+
}
36+
37+
print(path: AstPath<StorageLayoutSpecifier>, print: PrintFunction): Doc {
38+
return [
39+
'layout at',
40+
printSeparatedItem(path.call(print, 'expression'), {
41+
firstSeparator: line
42+
})
43+
];
44+
}
45+
}

src/slang-nodes/types.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import type { ContinueStatement } from './ContinueStatement.ts';
2929
import type { ContractDefinition } from './ContractDefinition.ts';
3030
import type { ContractMember } from './ContractMember.ts';
3131
import type { ContractMembers } from './ContractMembers.ts';
32+
import type { ContractSpecifier } from './ContractSpecifier.ts';
33+
import type { ContractSpecifiers } from './ContractSpecifiers.ts';
3234
import type { DecimalNumberExpression } from './DecimalNumberExpression.ts';
3335
import type { DoWhileStatement } from './DoWhileStatement.ts';
3436
import type { ElementaryType } from './ElementaryType.ts';
@@ -139,6 +141,7 @@ import type { StateVariableAttribute } from './StateVariableAttribute.ts';
139141
import type { StateVariableAttributes } from './StateVariableAttributes.ts';
140142
import type { StateVariableDefinition } from './StateVariableDefinition.ts';
141143
import type { StateVariableDefinitionValue } from './StateVariableDefinitionValue.ts';
144+
import type { StorageLayoutSpecifier } from './StorageLayoutSpecifier.ts';
142145
import type { StorageLocation } from './StorageLocation.ts';
143146
import type { StringExpression } from './StringExpression.ts';
144147
import type { StringLiteral } from './StringLiteral.ts';
@@ -257,6 +260,7 @@ export type StrictAstNode =
257260
| ContractDefinition
258261
| InheritanceSpecifier
259262
| InheritanceType
263+
| StorageLayoutSpecifier
260264
| InterfaceDefinition
261265
| LibraryDefinition
262266
| StructDefinition
@@ -411,6 +415,8 @@ export type StrictAstNode =
411415
| YulLiteral
412416
| SourceUnitMembers
413417
| VersionExpressionSet
418+
| ContractSpecifier
419+
| ContractSpecifiers
414420
| ContractMembers
415421
| InterfaceMembers
416422
| LibraryMembers
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { NonterminalKind } from '@nomicfoundation/slang/cst';
2+
3+
import type { ContractSpecifier } from '../slang-nodes/ContractSpecifier.js';
4+
5+
export function sortContractSpecifiers(
6+
a: ContractSpecifier,
7+
b: ContractSpecifier
8+
): number {
9+
const aVariant = a.variant;
10+
const bVariant = b.variant;
11+
12+
// OverrideSpecifiers before ModifierInvocation
13+
if (
14+
aVariant.kind === NonterminalKind.InheritanceSpecifier &&
15+
bVariant.kind === NonterminalKind.StorageLayoutSpecifier
16+
)
17+
return -1;
18+
if (
19+
bVariant.kind === NonterminalKind.InheritanceSpecifier &&
20+
aVariant.kind === NonterminalKind.StorageLayoutSpecifier
21+
)
22+
return 1;
23+
24+
return 0;
25+
}

0 commit comments

Comments
 (0)