Skip to content
Open
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
17 changes: 16 additions & 1 deletion src/render.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { buildSite, PageStructure, SiteConfig } from './site';
import { itemPartialName, render } from './render';
import { NodeType } from 'solidity-ast/node';
import { Templates } from './templates';
import * as defaultProperties from './common/properties';

interface TestSpec extends Templates {
collapseNewlines?: boolean;
Expand All @@ -22,7 +23,7 @@ function testRender(title: string, file: string, spec: TestSpec, expected: strin
};

test(title, t => {
const site = buildSite(t.context.build, cfg);
const site = buildSite(t.context.build, cfg, spec.properties ?? {});
const rendered = render(site, spec, spec.collapseNewlines);
t.is(rendered.length, 1);
t.is(rendered[0]!.contents, expected);
Expand Down Expand Up @@ -62,3 +63,17 @@ testRender('item partial',
},
'A, B, ',
);

// Solidity identifiers may contain '$'. ERC-7201 namespaced storage names its struct
// pointer '$', so `@return $ ...` has to resolve to that return parameter rather than
// throwing, and `@param a$b ...` has to attach to `a$b` rather than silently missing it.
testRender('dollar in parameter and return names',
'S08_Dollar',
{
properties: { ...defaultProperties },
partials: {
page: () => '{{#each items}}{{#each items}}{{#each params}}({{name}}:{{natspec}}){{/each}}{{#each returns}}[{{name}}:{{natspec}}]{{/each}}{{/each}}{{/each}}',
},
},
'[$:the storage struct pointer](a$b:the first operand)($c:the second operand)[$d:the sum]',
);
9 changes: 7 additions & 2 deletions src/utils/natspec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { ItemError } from './ItemError';
import { readItemDocs } from './read-item-docs';
import { getContractsInScope } from './scope';

// Solidity identifiers may contain '$' and may start with '$' or '_', which \w does not
// cover. ERC-7201 namespaced storage conventionally names its struct pointer '$', so a
// tag like `@return $ the storage struct` has to match here.
const IDENTIFIER = String.raw`[A-Za-z_$][A-Za-z0-9_$]*`;

export interface NatSpec {
title?: string;
notice?: string;
Expand Down Expand Up @@ -59,7 +64,7 @@ export function parseNatspec(item: DocItemWithContext): NatSpec {
}

if (tag === 'param') {
const paramMatches = content.match(/(\w+) ([^]*)/);
const paramMatches = content.match(new RegExp(`(${IDENTIFIER}) ([^]*)`));
if (paramMatches) {
const [, name, description] = paramMatches as [string, string, string];
res.params ??= [];
Expand All @@ -80,7 +85,7 @@ export function parseNatspec(item: DocItemWithContext): NatSpec {
if (!p.name) {
res.returns.push({ description: content.trim() });
} else {
const paramMatches = content.match(/(\w+)( ([^]*))?/);
const paramMatches = content.match(new RegExp(`(${IDENTIFIER})( ([^]*))?`));
if (!paramMatches || paramMatches[1] !== p.name) {
throw new ItemError(`Expected @return tag to start with name '${p.name}'`, item);
}
Expand Down
29 changes: 29 additions & 0 deletions test-contracts/S08_Dollar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

/// Identifiers may contain '$', as ERC-7201 namespaced storage does for its struct pointer.
contract Dollar {
struct Storage {
uint256 value;
}

/**
* @notice Loads the storage struct from its slot.
* @return $ the storage struct pointer
*/
function loadStorage() internal pure returns (Storage storage $) {
assembly {
$.slot := 0
}
}

/**
* @notice Takes a parameter whose name contains '$'.
* @param a$b the first operand
* @param $c the second operand
* @return $d the sum
*/
function add(uint256 a$b, uint256 $c) external pure returns (uint256 $d) {
$d = a$b + $c;
}
}