/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @emails react-core */ 'use strict'; const rule = require('../safe-string-coercion'); const {RuleTester} = require('eslint'); RuleTester.setDefaultConfig({ parser: require.resolve('babel-eslint'), parserOptions: { ecmaVersion: 6, sourceType: 'module', }, }); const ruleTester = new RuleTester(); const missingDevCheckMessage = 'Missing DEV check before this string coercion.' + ' Check should be in this format:\n' + ' if (__DEV__) {\n' + ' checkXxxxxStringCoercion(value);\n' + ' }'; const prevStatementNotDevCheckMessage = 'The statement before this coercion must be a DEV check in this format:\n' + ' if (__DEV__) {\n' + ' checkXxxxxStringCoercion(value);\n' + ' }'; const message = "Using `'' + value` or `value + ''` is fast to coerce strings, but may throw." + ' For prod code, add a DEV check from shared/CheckStringCoercion immediately' + ' before this coercion.' + ' For non-prod code and prod error handling, use `String(value)` instead.'; ruleTester.run('eslint-rules/safe-string-coercion', rule, { valid: [ { code: 'String(obj)', options: [{isProductionUserAppCode: false}], }, 'String(obj)', "'a' + obj", ` function getValueForAttribute( node, name, expected ) { if (__DEV__) { var value = node.getAttribute(name); if (__DEV__) { checkAttributeStringCoercion(expected, name); } if (value === '' + expected) { return expected; } return value; } } `, ` if (__DEV__) { checkFormFieldValueStringCoercion (obj) } '' + obj; `, ` function f(a, index) { if (typeof a === 'object' && a !== null && a.key != null) { if (__DEV__) { checkKeyStringCoercion(a.key); } return f('' + a.key); } return a; } `, "'' + i++", "'' + +i", "'' + +i", "+i + ''", "if (typeof obj === 'string') { '' + obj }", "if (typeof obj === 'string' || typeof obj === 'number') { '' + obj }", "if (typeof obj === 'string' && somethingElse) { '' + obj }", "if (typeof obj === 'number' && somethingElse) { '' + obj }", "if (typeof obj === 'bigint' && somethingElse) { '' + obj }", "if (typeof obj === 'undefined' && somethingElse) { '' + obj }", "if (typeof nextProp === 'number') { setTextContent(domElement, '' + nextProp); }", // These twe below are sneaky. The inner `if` is unsafe, but the outer `if` // ensures that the unsafe code will never be run. It's bad code, but // doesn't violate this rule. "if (typeof obj === 'string') { if (typeof obj === 'string' && obj.length) {} else {'' + obj} }", "if (typeof obj === 'string') if (typeof obj === 'string' && obj.length) {} else {'' + obj}", "'' + ''", "'' + '' + ''", "`test${foo}` + ''", ], invalid: [ { code: "'' + obj", errors: [ { message: missingDevCheckMessage + '\n' + message, }, ], }, { code: "obj + ''", errors: [ { message: missingDevCheckMessage + '\n' + message, }, ], }, { code: 'String(obj)', options: [{isProductionUserAppCode: true}], errors: [ { message: "For perf-sensitive coercion, avoid `String(value)`. Instead, use `'' + value`." + ' Precede it with a DEV check from shared/CheckStringCoercion' + ' unless Symbol and Temporal.* values are impossible.' + ' For non-prod code and prod error handling, use `String(value)` and disable this rule.', }, ], }, { code: "if (typeof obj === 'object') { '' + obj }", errors: [ { message: missingDevCheckMessage + '\n' + message, }, ], }, { code: "if (typeof obj === 'string') { } else if (typeof obj === 'object') {'' + obj}", errors: [ { message: missingDevCheckMessage + '\n' + message, }, ], }, { code: "if (typeof obj === 'string' && obj.length) {} else {'' + obj}", errors: [ { message: missingDevCheckMessage + '\n' + message, }, ], }, { code: ` if (__D__) { checkFormFieldValueStringCoercion (obj) } '' + obj; `, errors: [ { message: prevStatementNotDevCheckMessage + '\n' + message, }, ], }, { code: ` if (__DEV__) { checkFormFieldValueStringCoercion (obj) } '' + notobjj; `, errors: [ { message: 'Value passed to the check function before this coercion must match the value being coerced.' + '\n' + message, }, ], }, { code: ` if (__DEV__) { checkFormFieldValueStringCoercion (obj) } // must be right before the check call someOtherCode(); '' + objj; `, errors: [ { message: prevStatementNotDevCheckMessage + '\n' + message, }, ], }, { code: ` if (__DEV__) { chexxxxBadNameCoercion (obj) } '' + objj; `, errors: [ { message: 'Missing or invalid check function call before this coercion.' + ' Expected: call of a function like checkXXXStringCoercion. ' + prevStatementNotDevCheckMessage + '\n' + message, }, ], }, { code: ` if (__DEV__) { } '' + objj; `, errors: [ { message: prevStatementNotDevCheckMessage + '\n' + message, }, ], }, { code: ` if (__DEV__) { if (x) {} } '' + objj; `, errors: [ { message: 'The DEV block before this coercion must only contain an expression. ' + prevStatementNotDevCheckMessage + '\n' + message, }, ], }, { code: ` if (a) { if (__DEV__) { // can't have additional code before the check call if (b) { checkKeyStringCoercion(obj); } } g = f( c, d + (b ? '' + obj : '') + e); } `, errors: [ { message: 'The DEV block before this coercion must only contain an expression. ' + prevStatementNotDevCheckMessage + '\n' + message, }, ], }, { code: ` if (__DEV__) { checkAttributeStringCoercion(expected, name); } // DEV check should be inside the if block if (a && b) { f('' + expected); } `, errors: [ { message: missingDevCheckMessage + '\n' + message, }, ], }, { code: `'' + obj + ''`, errors: [ {message: missingDevCheckMessage + '\n' + message}, {message: missingDevCheckMessage + '\n' + message}, ], }, { code: `foo\`text\` + ""`, errors: [{message: missingDevCheckMessage + '\n' + message}], }, ], });