solhint-community
Version:
Solidity Code Linter
514 lines (469 loc) • 19.5 kB
JavaScript
const assert = require('assert')
const { processStr } = require('../../../lib/index')
const { contractWith, funcWith } = require('../../common/contract-builder')
const { configGetter } = require('../../../lib/config/config-file')
describe('style-guide-casing', function () {
describe('immutable names should be in SNAKE_CASE', () => {
it('should raise error when variable is in camelCase', () => {
const code = contractWith('uint32 private immutable camelCase;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(
report.messages[0].message.includes(
'Immutable variables name should be capitalized SNAKE_CASE'
)
)
})
it('should NOT raise error when variable is in camelCase AND config ignores immutables', () => {
const code = contractWith('uint32 private immutable camelCase;')
const report = processStr(code, {
rules: { 'style-guide-casing': ['error', { ignoreImmutables: true }] },
})
assert.equal(report.errorCount, 0)
})
it('should NOT raise error when variable is in SNAKE_CASE and has a trailing underscore', () => {
const code = contractWith('uint32 private immutable SNAKE_CASE_;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should raise error when variable is in lowercase_snakecase', () => {
const code = contractWith('uint32 private immutable snake_case;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(
report.messages[0].message.includes(
'Immutable variables name should be capitalized SNAKE_CASE'
)
)
})
describe('Immutable variable with $ character as SNAKE_CASE', () => {
const WITH_$ = {
'starting with $': contractWith('uint32 immutable private $_D;'),
'starting with $D': contractWith('uint32 immutable private $D;'),
'containing a $': contractWith('uint32 immutable private TEST_WITH_$_CONTAINED;'),
'ending with $': contractWith('uint32 immutable private TEST_WITH_ENDING_$;'),
'ending with D$': contractWith('uint32 immutable private TEST_WITH_ENDING_D$;'),
'only with $': contractWith('uint32 immutable private $;'),
}
for (const [key, code] of Object.entries(WITH_$)) {
it(`should not raise immutable error for variables ${key}`, () => {
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
}
})
})
describe('constant names should be in SNAKE_CASE', () => {
it('should raise const name error', () => {
const code = contractWith('uint private constant a;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('SNAKE_CASE'))
})
it('should NOT raise error when variable is in camelCase AND config ignores constants', () => {
const code = contractWith('uint32 private constant camelCase;')
const report = processStr(code, {
rules: { 'style-guide-casing': ['error', { ignoreConstants: true }] },
})
assert.equal(report.errorCount, 0)
})
it('should not raise const name error for constants in snake case', () => {
const code = contractWith('uint32 private constant THE_CONSTANT = 10;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should not raise const name error for constants in snake case with a trailing underscore', () => {
const code = contractWith('uint32 private constant THE_CONSTANT_ = 10;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should not raise const name error for constants in snake case with single leading underscore', () => {
const code = contractWith('uint32 private constant _THE_CONSTANT = 10;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should not raise const name error for constants in snake case with double leading underscore', () => {
const code = contractWith('uint32 private constant __THE_CONSTANT = 10;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should raise const name error for constants in snake case with more than two leading underscores', () => {
const code = contractWith('uint32 private constant ___THE_CONSTANT = 10;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('SNAKE_CASE'))
})
describe('constant name with $ character', () => {
const WITH_$ = {
'starting with $': contractWith('uint32 private constant $THE_CONSTANT = 10;'),
'containing a $': contractWith('uint32 private constant THE_$_CONSTANT = 10;'),
'ending with $': contractWith('uint32 private constant THE_CONSTANT$ = 10;'),
'only with $': contractWith('uint32 private constant $ = 10;'),
}
for (const [key, code] of Object.entries(WITH_$)) {
it(`should not raise error for ${key}`, () => {
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
}
})
})
describe('function names should be in mixedCase', () => {
describe('disabling rule for different visibilities in config', function () {
;[
{
configKey: 'ignoreExternalFunctions',
code: contractWith('function foo_bar() external {}'),
name: 'external',
},
{
configKey: 'ignoreInternalFunctions',
code: contractWith('function foo_bar() internal {}'),
name: 'internal',
},
{
configKey: 'ignorePublicFunctions',
code: contractWith('function foo_bar() public {}'),
name: 'public',
},
{
configKey: 'ignorePrivateFunctions',
code: contractWith('function foo_bar() private {}'),
name: 'private',
},
{
configKey: 'ignoreInternalFunctions',
code: 'function foo_bar() {}',
name: 'free',
},
].forEach(({ configKey, code, name }) => {
it(`should not raise error for ${name} function`, function () {
const report = processStr(code, { [configKey]: true })
assert.equal(report.errorCount, 0)
})
})
})
it('should raise func name error when a free function is in CapWords', () => {
const code = 'function AFuncName () {}'
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('mixedCase'))
})
it('should raise func name error when a contract function is in CapWords', () => {
const code = contractWith('function AFuncName () public {}')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('mixedCase'))
})
it('should not raise incorrect func name error', () => {
const code = contractWith('function aFunc1Nam23e () public {}')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
describe('Function names with $ character', () => {
const WITH_$ = {
'starting with $': contractWith('function $aFunc1Nam23e () public {}'),
'containing a $': contractWith('function aFunc$1Nam23e () public {}'),
'ending with $': contractWith('function aFunc1Nam23e$ () public {}'),
'only with $': contractWith('function $() public {}'),
}
for (const [key, code] of Object.entries(WITH_$)) {
it(`should not raise func name error for Functions ${key}`, () => {
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
}
})
it('should allow pure functions to be in UPPER_SNAKE_CASE', () => {
const code = contractWith('function FOO_BAR() public pure returns (uint256) {}')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should allow interface functions with same name as immutable to be in UPPER_SNAKE_CASE', () => {
const code = 'interface IFoo { function FOO_BAR() external pure returns (address); }'
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should raise error for pure functions not in mixedCase or UPPER_SNAKE_CASE', () => {
const code = contractWith('function snake_case() public pure returns (uint256) {}')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(
report.messages[0].message.includes(
'Pure function name must be in mixedCase or UPPER_SNAKE_CASE'
)
)
})
it('should raise error for pure interface functions not in mixedCase or UPPER_SNAKE_CASE', () => {
const code = 'interface IFoo { function snake_case() external pure returns (address); }'
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(
report.messages[0].message.includes(
'Pure function name must be in mixedCase or UPPER_SNAKE_CASE'
)
)
})
})
describe('modifier names should be in mixedCase', () => {
it('should NOT raise error when modifier name is snake_case AND config ignores it', () => {
const code = contractWith('modifier snake_case(address a) { }')
const report = processStr(code, {
rules: { 'style-guide-casing': ['error', { ignoreModifiers: true }] },
})
assert.equal(report.errorCount, 0)
})
;['snake_case', 'twoTrailingUnderscores__', 'threeTrailingUnderscores___'].forEach((name) => {
it(`should raise modifier name error on ${name}`, () => {
const code = contractWith(`modifier ${name}(address a) { }`)
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('mixedCase'))
})
})
;[
'mixedCase',
'mixedCaseWithOneTrailingUnderscore_',
'_leadingUnderscore',
'__twoLeadingUnderscores',
'___threeLeadingUnderscores',
'trailingUnderscore_',
].forEach((name) => {
it(`should not raise modifier name error on ${name}`, () => {
const code = contractWith(`modifier ${name}(address a) { }`)
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
})
describe('with $ character', () => {
const WITH_$ = {
'starting with $': contractWith('modifier $ownedBy(address a) { }'),
'containing a $': contractWith('modifier owned$By(address a) { }'),
'ending with $': contractWith('modifier ownedBy$(address a) { }'),
'only with $': contractWith('modifier $(uint a) { }'),
}
for (const [key, code] of Object.entries(WITH_$)) {
it(`should not raise func name error for Modifiers ${key}`, () => {
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
}
})
})
describe('mutable variable names should be in mixedCase', () => {
it('should NOT raise error when variable is in camelCase AND config ignores variables', () => {
const code = contractWith('uint32 camelCase;')
const report = processStr(code, {
rules: { 'style-guide-casing': ['error', { ignoreVariables: true }] },
})
assert.equal(report.errorCount, 0)
})
it('should raise incorrect var name error', () => {
const code = funcWith('var (a, B);')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.ok(report.errorCount > 0)
assert.equal(report.messages[0].message, 'Variable name must be in mixedCase')
})
it('should raise incorrect func param name error', () => {
const code = contractWith('function funcName (uint A) public {}')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('name'))
})
it('should raise var name error for event arguments illegal styling', () => {
const code = contractWith('event Event1(uint B);')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('mixedCase'))
})
it('should NOT raise var name error for variable with trailing underscore', () => {
const code = contractWith('uint256 private foo_;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should raise incorrect var name error for typed declaration', () => {
const code = funcWith('uint B = 1;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.ok(report.errorCount > 0)
assert.equal(report.messages[0].message, 'Variable name must be in mixedCase')
})
it('should raise incorrect var name error for state declaration', () => {
const code = contractWith('uint32 private D = 10;')
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('Variable name'))
})
describe('with $ character', () => {
const WITH_$ = {
'starting with $': contractWith('uint32 private $D = 10;'),
'containing a $': contractWith('uint32 private testWith$Contained = 10;'),
'ending with $': contractWith('uint32 private testWithEnding$ = 10;'),
'only with $': contractWith('uint32 private $;'),
}
for (const [key, code] of Object.entries(WITH_$)) {
it(`should not raise var name error for variables ${key}`, () => {
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
}
})
})
describe('type definition names should be in CapWords', () => {
;[
{ name: 'Contracts', correct: 'contract FooBar{}', incorrect: 'contract foobar {}' },
{ name: 'Interfaces', correct: 'interface FooBar{}', incorrect: 'interface foobar {}' },
{ name: 'Interfaces', correct: 'interface FooBar{}', incorrect: 'interface foobar {}' },
{ name: 'Interfaces', correct: 'interface FooBar_{}', incorrect: 'interface foobar_ {}' },
{ name: 'Libraries', correct: 'library FooBar{}', incorrect: 'library foobar {}' },
{
name: 'Enums',
correct: contractWith('enum Abc { Value, OtherValue }'),
incorrect: contractWith('enum abc { Value, OtherValue }'),
},
{
name: 'Events',
correct: contractWith('event SomethingHappened();'),
incorrect: contractWith('event somethingHappened();'),
},
{
name: 'Structs',
correct: contractWith('struct Thingy{uint256 a;}'),
incorrect: contractWith('struct thingy{uint256 a;}'),
},
].forEach(({ name, correct, incorrect }) => {
it(`should raise error for ${name} with invalid name`, () => {
const report = processStr(incorrect, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(report.messages[0].message.includes('CapWords'))
})
it(`should NOT raise error for ${name} with valid name`, () => {
const report = processStr(correct, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it(`should NOT raise error for ${name} with invalid name AND a config disabling it`, () => {
const report = processStr(incorrect, {
rules: { 'style-guide-casing': ['error', { [`ignore${name}`]: true }] },
})
assert.equal(report.errorCount, 0)
})
})
})
describe('name with $ character', () => {
const WITH_$ = {
'starting with $': contractWith('struct $MyStruct {}'),
'containing a $': contractWith('struct My$Struct {}'),
'ending with $': contractWith('struct MyStruct$ {}'),
'only with $': contractWith('struct $ {}'),
}
for (const [key, code] of Object.entries(WITH_$)) {
it(`should not raise contract name error for Structs ${key}`, () => {
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
}
})
it('rule is included in solhint:recommended ruleset with warning severity', function () {
const report = processStr('contract foo_bar{}', {
rules: {
...configGetter('solhint:recommended').rules,
'compiler-version': 'off',
'no-empty-blocks': 'off',
},
})
assert.equal(report.warningCount, 1)
assert.ok(report.messages[0].message.includes('CapWords'))
})
describe('pure free functions', function () {
it('should NOT raise error if a pure free function is in UPPER_SNAKE_CASE', () => {
const code = `
function FOO_BAR() pure returns (uint256) {
return 42;
}
`
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 0)
})
it('should raise error if a pure free function is in snake_case', () => {
const code = `
function snake_case() pure returns (uint256) {
return 42;
}
`
const report = processStr(code, {
rules: { 'style-guide-casing': 'error' },
})
assert.equal(report.errorCount, 1)
assert.ok(
report.messages[0].message.includes(
'Pure function name must be in mixedCase or UPPER_SNAKE_CASE'
)
)
})
})
})