Skip to content

Commit bb862e4

Browse files
committed
implement custom lint rule
1 parent 42f49a8 commit bb862e4

File tree

4 files changed

+116
-1
lines changed

4 files changed

+116
-1
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ module.exports = {
162162
'aws-toolkits/no-incorrect-once-usage': 'error',
163163
'aws-toolkits/no-string-exec-for-child-process': 'error',
164164
'aws-toolkits/no-console-log': 'error',
165-
165+
'aws-toolkits/no-json-stringify-in-log': 'error',
166166
'no-restricted-imports': [
167167
'error',
168168
{

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import NoIncorrectOnceUsage from './lib/rules/no-incorrect-once-usage'
99
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'
12+
import noJsonStringifyInLog from './lib/rules/no-json-stringify-in-log'
1213

1314
const rules = {
1415
'no-await-on-vscode-msg': NoAwaitOnVscodeMsg,
@@ -17,6 +18,7 @@ const rules = {
1718
'no-only-in-tests': NoOnlyInTests,
1819
'no-string-exec-for-child-process': NoStringExecForChildProcess,
1920
'no-console-log': NoConsoleLog,
21+
'no-json-stringify-in-log': noJsonStringifyInLog,
2022
}
2123

2224
export { rules }
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { AST_NODE_TYPES, ESLintUtils } from '@typescript-eslint/utils'
7+
import { Rule } from 'eslint'
8+
9+
export const errMsg = 'Avoid using JSON.stringify within logging and error messages, prefer %O.'
10+
11+
function isJsonStringifyCall(node: any) {
12+
return (
13+
node.type === AST_NODE_TYPES.CallExpression &&
14+
node.callee.type === AST_NODE_TYPES.MemberExpression &&
15+
node.callee.object.type === AST_NODE_TYPES.Identifier &&
16+
node.callee.object.name === 'JSON' &&
17+
node.callee.property.type === AST_NODE_TYPES.Identifier &&
18+
node.callee.property.name === 'stringify'
19+
)
20+
}
21+
22+
export default ESLintUtils.RuleCreator.withoutDocs({
23+
meta: {
24+
docs: {
25+
description: 'disallow use of JSON.stringify in logs and errors, to encourage use %O',
26+
recommended: 'recommended',
27+
},
28+
messages: {
29+
errMsg,
30+
},
31+
type: 'problem',
32+
fixable: 'code',
33+
schema: [],
34+
},
35+
defaultOptions: [],
36+
create(context) {
37+
return {
38+
CallExpression(node) {
39+
// Look for a getLogger().f() call.
40+
if (
41+
node.callee.type === AST_NODE_TYPES.MemberExpression &&
42+
node.callee.object.type === AST_NODE_TYPES.CallExpression &&
43+
node.callee.object.callee.type === AST_NODE_TYPES.Identifier &&
44+
node.callee.object.callee.name === 'getLogger'
45+
) {
46+
// For each argument to the call above, check if it contains a JSON.stringify
47+
node.arguments.forEach((arg) => {
48+
// Check if arg itself if a JSON.stringify call
49+
if (isJsonStringifyCall(arg)) {
50+
return context.report({
51+
node: node,
52+
messageId: 'errMsg',
53+
})
54+
}
55+
// Check if the arg contains a template ex. '${...}'
56+
if (arg.type === AST_NODE_TYPES.TemplateLiteral) {
57+
arg.expressions.forEach((e) => {
58+
// Check the template for a JSON.stringify call.
59+
if (isJsonStringifyCall(e)) {
60+
return context.report({
61+
node: node,
62+
messageId: 'errMsg',
63+
})
64+
}
65+
})
66+
}
67+
})
68+
}
69+
},
70+
}
71+
},
72+
}) as unknown as Rule.RuleModule
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 { errMsg } from '../../lib/rules/no-json-stringify-in-log'
8+
import { getRuleTester } from '../testUtil'
9+
10+
getRuleTester().run('no-json-stringify-in-log', rules['no-json-stringify-in-log'], {
11+
valid: [
12+
'getLogger().debug(`the object is %O`)',
13+
'return JSON.stringify(d)',
14+
'getLogger().debug(`this does not include anything`)',
15+
'getLogger().debug(`another example ${JSON.notString(something)}`)',
16+
'getLogger().fakeFunction(`another example ${JSON.notString(something)}`)',
17+
],
18+
19+
invalid: [
20+
{
21+
code: 'getLogger().debug(`the object is ${JSON.stringify(obj)}`)',
22+
errors: [errMsg],
23+
},
24+
{
25+
code: 'getLogger().debug(`the object is ${notThis} but ${JSON.stringify(obj)}`)',
26+
errors: [errMsg],
27+
},
28+
{
29+
code: 'getLogger().debug(`the object is ${notThis} or ${thisOne} but ${JSON.stringify(obj)}`)',
30+
errors: [errMsg],
31+
},
32+
{
33+
code: 'getLogger().verbose(`Invalid Request : `, JSON.stringify(request, undefined, EditorContext.getTabSize()))',
34+
errors: [errMsg],
35+
},
36+
{
37+
code: 'getLogger().verbose(`provideDebugConfigurations: debugconfigs: ${JSON.stringify(configs)}`)',
38+
errors: [errMsg],
39+
},
40+
],
41+
})

0 commit comments

Comments
 (0)