diff --git a/src/rules/no-unsafe-values.js b/src/rules/no-unsafe-values.js new file mode 100644 index 0000000..85a21f5 --- /dev/null +++ b/src/rules/no-unsafe-values.js @@ -0,0 +1,60 @@ +/** + * @fileoverview Rule to detect unsafe values in JSON. + * @author Bradley Meck Farias + */ + +export default { + meta: { + type: "problem", + + docs: { + description: "Disallow JSON values that are unsafe for interchange", + }, + + messages: { + unsafeNumber: "Number outside safe range found.", + loneSurrogate: "Lone surrogate '{{ surrogate }}' found.", + }, + }, + + create(context) { + return { + Number(node) { + if (Number.isFinite(node.value) !== true) { + context.report({ + loc: node.loc, + messageId: "unsafeNumber", + }); + } + }, + String(node) { + if (node.value.isWellFormed) { + if (node.value.isWellFormed()) { + return; + } + } + // match any high surrogate and, if it exists, a paired low surrogate + // match any low surrogate not already matched + const surrogatePattern = + /[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/gu; + let match = surrogatePattern.exec(node.value); + while (match) { + // only need to report non-paired surrogates + if (match[0].length < 2) { + context.report({ + loc: node.loc, + messageId: "loneSurrogate", + data: { + surrogate: JSON.stringify(match[0]).slice( + 1, + -1, + ), + }, + }); + } + match = surrogatePattern.exec(node.value); + } + }, + }; + }, +}; diff --git a/tests/rules/no-unsafe-values.test.js b/tests/rules/no-unsafe-values.test.js new file mode 100644 index 0000000..4cc9426 --- /dev/null +++ b/tests/rules/no-unsafe-values.test.js @@ -0,0 +1,132 @@ +/** + * @fileoverview Tests for no-empty-keys rule. + * @author Bradley Meck Farias + */ + +//------------------------------------------------------------------------------ +// Imports +//------------------------------------------------------------------------------ + +import rule from "../../src/rules/no-unsafe-values.js"; +import json from "../../src/index.js"; +import { RuleTester } from "eslint"; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + plugins: { + json, + }, + language: "json/json", +}); + +ruleTester.run("no-unsafe-values", rule, { + valid: [ + "123", + { + code: "1234", + language: "json/json5", + }, + { + code: "12345", + language: "json/json5", + }, + '"🔥"', + '"\\ud83d\\udd25"', + ], + invalid: [ + { + code: "2e308", + errors: [ + { + messageId: "unsafeNumber", + line: 1, + column: 1, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: "-2e308", + errors: [ + { + messageId: "unsafeNumber", + line: 1, + column: 1, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: '"\ud83d"', + errors: [ + { + messageId: "loneSurrogate", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + }, + ], + }, + { + code: '"\\ud83d"', + errors: [ + { + messageId: "loneSurrogate", + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: '"\udd25"', + errors: [ + { + messageId: "loneSurrogate", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + }, + ], + }, + { + code: '"\\udd25"', + errors: [ + { + messageId: "loneSurrogate", + line: 1, + column: 1, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: '"\ud83d\ud83d"', + errors: [ + { + message: "Lone surrogate '\\ud83d' found.", + line: 1, + column: 1, + endLine: 1, + endColumn: 5, + }, + { + message: "Lone surrogate '\\ud83d' found.", + line: 1, + column: 1, + endLine: 1, + endColumn: 5, + }, + ], + }, + ], +});