Skip to content

Commit aaad8ad

Browse files
committed
implement rule
1 parent 8afa3d8 commit aaad8ad

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

plugins/eslint-plugin-aws-toolkits/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import NoOnlyInTests from './lib/rules/no-only-in-tests'
1010
import NoStringExecForChildProcess from './lib/rules/no-string-exec-for-child-process'
1111
import NoConsoleLog from './lib/rules/no-console-log'
1212
import noJsonStringifyInLog from './lib/rules/no-json-stringify-in-log'
13+
import noStringSubMismatch from './lib/rules/no-string-sub-mismatch'
1314

1415
const rules = {
1516
'no-await-on-vscode-msg': NoAwaitOnVscodeMsg,
@@ -19,6 +20,7 @@ const rules = {
1920
'no-string-exec-for-child-process': NoStringExecForChildProcess,
2021
'no-console-log': NoConsoleLog,
2122
'no-json-stringify-in-log': noJsonStringifyInLog,
23+
'no-string-sub-mismatch': noStringSubMismatch,
2224
}
2325

2426
export { rules }

plugins/eslint-plugin-aws-toolkits/lib/rules/no-string-sub-mismatch.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,61 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'
5+
import { AST_NODE_TYPES, ESLintUtils, TSESTree } from '@typescript-eslint/utils'
6+
import { isLoggerCall } from './no-json-stringify-in-log'
7+
import { Rule } from 'eslint'
8+
9+
/**
10+
* reuse solution done in logger itself: https://github.com/winstonjs/winston/blob/195e55c7e7fc58914ae4967ea7b832c9e0ced930/lib/winston/logger.js#L27
11+
*/
12+
function countSubTokens(literalNode: TSESTree.StringLiteral) {
13+
const formatRegExp = /%[scdjifoO%]/g
14+
return literalNode.value.match(formatRegExp)?.length || 0
15+
}
16+
/**
17+
* Form the error message using templates or actual values.
18+
* Allows us to avoid copy pasting message into test file.
19+
*/
20+
export function formErrorMsg(substitutionTokens: string | number, numArgs: string | number): string {
21+
return `String substitution has ${substitutionTokens} substitution tokens, but ${numArgs} arguments.`
22+
}
623

724
export default ESLintUtils.RuleCreator.withoutDocs({
825
meta: {
926
docs: {
1027
description: 'ensure string substitution args and templates match',
1128
recommended: 'recommended',
1229
},
13-
messages: {},
30+
messages: {
31+
errMsg: formErrorMsg('{{ substitutionTokens }}', '{{ args }}'),
32+
},
1433
type: 'problem',
1534
fixable: 'code',
1635
schema: [],
1736
},
1837
defaultOptions: [],
1938
create(context) {
20-
return {}
39+
return {
40+
CallExpression(node: TSESTree.CallExpression) {
41+
if (
42+
isLoggerCall(node) &&
43+
node.arguments[0].type === AST_NODE_TYPES.Literal &&
44+
typeof node.arguments[0].value === 'string'
45+
) {
46+
const numSubTokens = countSubTokens(node.arguments[0])
47+
const numExtraArgs = node.arguments.length - 1
48+
if (numSubTokens !== numExtraArgs) {
49+
return context.report({
50+
node: node,
51+
data: {
52+
substitutionTokens: numSubTokens,
53+
args: numExtraArgs,
54+
},
55+
messageId: 'errMsg',
56+
})
57+
}
58+
}
59+
},
60+
}
2161
},
22-
})
62+
}) as unknown as Rule.RuleModule
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { rules } from '../../index'
7+
import { formErrorMsg } from '../../lib/rules/no-string-sub-mismatch'
8+
import { getRuleTester } from '../testUtil'
9+
10+
getRuleTester().run('no-string-sub-mismatch', rules['no-string-sub-mismatch'], {
11+
valid: [
12+
'getLogger().debug("this is a string %s and a number %d", "s", 2)',
13+
'getLogger().debug("this is a number %d", 2)',
14+
'getLogger().debug("this has no substitutions")',
15+
'getLogger().debug("1 %s 2 %d 3 %O 4 %o 5 %s", arg1, arg2, arg3, arg4, arg5)',
16+
'getLogger().debug("not real a sub-token %z")',
17+
],
18+
invalid: [
19+
{
20+
code: 'getLogger().debug("this is a string %s and a number %d", "s")',
21+
errors: [formErrorMsg(2, 1)],
22+
},
23+
{
24+
code: 'getLogger().debug("this is a string %s a string %s a string %s")',
25+
errors: [formErrorMsg(3, 0)],
26+
},
27+
{
28+
code: 'getLogger().debug("this is a string", err)',
29+
errors: [formErrorMsg(0, 1)],
30+
},
31+
],
32+
})

0 commit comments

Comments
 (0)