diff --git a/dist/no-pattern-match.js b/dist/no-pattern-match.js index 03b5e76..e6db5b6 100644 --- a/dist/no-pattern-match.js +++ b/dist/no-pattern-match.js @@ -17,10 +17,10 @@ function globalizeAllRegularExps(patterns) { globalizeRegularExpression(pattern), ])); } -function parseAndValidateOptions({ additionalPatterns }) { - const compiledRegexes = (0, utils_1.validateRecordOfRegex)((0, utils_1.plainObjectOption)(additionalPatterns, "additionalPatterns", utils_1.DEFAULT_ADDTIONAL_REGEXES)); +function parseAndValidateOptions({ patterns }) { + const compiledRegexes = (0, utils_1.validateRecordOfRegex)((0, utils_1.plainObjectOption)(patterns, "patterns", utils_1.DEFAULT_ADDTIONAL_REGEXES)); return { - additionalPatterns: compiledRegexes, + patterns: compiledRegexes, }; } function findAllNewLines(text) { @@ -76,9 +76,46 @@ function findLineAndColNoFromMatchIdx(startIdx, linesIdx, matchLength) { } return { endIdx, startIdx, lineSelections }; } +function serializeTextSelections(textAreaSelection) { + return textAreaSelection.lineSelections + .map((line) => { + return `${line.lineNo}:${line.startCol}-${line.endCol}`; + }) + .join(","); +} +function findStartAndEndTextSelection(textAreaSelection) { + const start = { + column: Infinity, + line: Infinity, + }; + const end = { + line: 0, + column: 0, + }; + for (const line of textAreaSelection.lineSelections) { + const min = Math.min(line.lineNo, start.line); + if (line.lineNo === min) { + start.line = min; + start.column = line.startCol; + } + const max = Math.max(line.lineNo, end.line); + if (line.lineNo === max) { + end.line = max; + end.column = line.endCol; + } + } + return { + start, + end, + }; +} +const FULL_TEXT_MATCH_MESSAGE = `Found text that matches the pattern "{{ patternName }}": {{ textMatch }}`; const noPatternMatch = { meta: { schema: false, + messages: { + [utils_1.FULL_TEXT_MATCH]: FULL_TEXT_MATCH_MESSAGE, + }, docs: { description: "An eslint rule that does pattern matching against an entire file", category: "Best Practices", @@ -86,21 +123,30 @@ const noPatternMatch = { }, create(context) { var _a; - const { additionalPatterns } = parseAndValidateOptions(context.options[0] || {}); + const { patterns } = parseAndValidateOptions(context.options[0] || {}); const sourceCode = ((_a = context === null || context === void 0 ? void 0 : context.getSourceCode) === null || _a === void 0 ? void 0 : _a.call(context)) || context.sourceCode; - const patterns = Object.entries(additionalPatterns); + const patternList = Object.entries(patterns); const text = sourceCode.text; const newLinePos = findAllNewLines(text); - for (const [name, pattern] of patterns) { + const matches = patternList + .map(([name, pattern]) => { const globalPattern = globalizeRegularExpression(pattern); const matches = Array.from(text.matchAll(globalPattern)); - for (const m of matches) { + return matches.map((m) => { const idx = m.index; - const match = m[0]; - const meta = findLineAndColNoFromMatchIdx(idx, newLinePos, match.length); - //console.dir({ match, meta }, { depth: 3 }); - } - } + const textMatch = m[0]; + const lineAndColNumbers = findLineAndColNoFromMatchIdx(idx, newLinePos, textMatch.length); + return { lineAndColNumbers, textMatch, patternName: name }; + }); + }) + .flat(); + matches.forEach(({ patternName, textMatch, lineAndColNumbers }) => { + context.report({ + data: { patternName, textMatch }, + messageId: utils_1.FULL_TEXT_MATCH, + loc: findStartAndEndTextSelection(lineAndColNumbers), + }); + }); return {}; }, }; diff --git a/dist/utils.js b/dist/utils.js index 302ae4a..52aea90 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.PATTERN_MATCH = exports.HIGH_ENTROPY = exports.DEFAULT_ADDTIONAL_REGEXES = void 0; +exports.FULL_TEXT_MATCH = exports.PATTERN_MATCH = exports.HIGH_ENTROPY = exports.DEFAULT_ADDTIONAL_REGEXES = void 0; exports.isPlainObject = isPlainObject; exports.plainObjectOption = plainObjectOption; exports.validateRecordOfRegex = validateRecordOfRegex; @@ -164,3 +164,4 @@ function getAssignmentName(node) { } exports.HIGH_ENTROPY = "HIGH_ENTROPY"; exports.PATTERN_MATCH = "PATTERN_MATCH"; +exports.FULL_TEXT_MATCH = "FULL_TEXT_MATCH"; diff --git a/src/no-pattern-match.ts b/src/no-pattern-match.ts index 05e3cff..c0b8c3c 100644 --- a/src/no-pattern-match.ts +++ b/src/no-pattern-match.ts @@ -1,6 +1,7 @@ import type { Rule } from "eslint"; import { DEFAULT_ADDTIONAL_REGEXES, + FULL_TEXT_MATCH, plainObjectOption, validateRecordOfRegex, } from "./utils"; @@ -26,16 +27,12 @@ function globalizeAllRegularExps( ); } -function parseAndValidateOptions({ additionalPatterns }) { +function parseAndValidateOptions({ patterns }) { const compiledRegexes = validateRecordOfRegex( - plainObjectOption( - additionalPatterns, - "additionalPatterns", - DEFAULT_ADDTIONAL_REGEXES - ) + plainObjectOption(patterns, "patterns", DEFAULT_ADDTIONAL_REGEXES) ); return { - additionalPatterns: compiledRegexes, + patterns: compiledRegexes, }; } @@ -107,9 +104,49 @@ function findLineAndColNoFromMatchIdx( return { endIdx, startIdx, lineSelections }; } +function serializeTextSelections(textAreaSelection: TextAreaSelection) { + return textAreaSelection.lineSelections + .map((line) => { + return `${line.lineNo}:${line.startCol}-${line.endCol}`; + }) + .join(","); +} + +function findStartAndEndTextSelection(textAreaSelection: TextAreaSelection) { + const start = { + column: Infinity, + line: Infinity, + }; + const end = { + line: 0, + column: 0, + }; + for (const line of textAreaSelection.lineSelections) { + const min = Math.min(line.lineNo, start.line); + if (line.lineNo === min) { + start.line = min; + start.column = line.startCol; + } + const max = Math.max(line.lineNo, end.line); + if (line.lineNo === max) { + end.line = max; + end.column = line.endCol; + } + } + return { + start, + end, + }; +} + +const FULL_TEXT_MATCH_MESSAGE = `Found text that matches the pattern "{{ patternName }}": {{ textMatch }}`; + const noPatternMatch: Rule.RuleModule = { meta: { schema: false, + messages: { + [FULL_TEXT_MATCH]: FULL_TEXT_MATCH_MESSAGE, + }, docs: { description: "An eslint rule that does pattern matching against an entire file", @@ -117,29 +154,35 @@ const noPatternMatch: Rule.RuleModule = { }, }, create(context) { - const { additionalPatterns } = parseAndValidateOptions( - context.options[0] || {} - ); + const { patterns } = parseAndValidateOptions(context.options[0] || {}); const sourceCode = context?.getSourceCode?.() || context.sourceCode; - const patterns = Object.entries(additionalPatterns); + const patternList = Object.entries(patterns); const text = sourceCode.text; const newLinePos = findAllNewLines(text); + const matches = patternList + .map(([name, pattern]) => { + const globalPattern = globalizeRegularExpression(pattern); + const matches = Array.from(text.matchAll(globalPattern)); + return matches.map((m) => { + const idx = m.index; + const textMatch = m[0]; + const lineAndColNumbers = findLineAndColNoFromMatchIdx( + idx, + newLinePos, + textMatch.length + ); - for (const [name, pattern] of patterns) { - const globalPattern = globalizeRegularExpression(pattern); - const matches = Array.from(text.matchAll(globalPattern)); - for (const m of matches) { - const idx = m.index; - const match = m[0]; - const meta = findLineAndColNoFromMatchIdx( - idx, - newLinePos, - match.length - ); - //console.dir({ match, meta }, { depth: 3 }); - } - } - + return { lineAndColNumbers, textMatch, patternName: name }; + }); + }) + .flat(); + matches.forEach(({ patternName, textMatch, lineAndColNumbers }) => { + context.report({ + data: { patternName, textMatch }, + messageId: FULL_TEXT_MATCH, + loc: findStartAndEndTextSelection(lineAndColNumbers), + }); + }); return {}; }, }; diff --git a/src/utils.ts b/src/utils.ts index cf532e6..e05059f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -207,3 +207,5 @@ function getAssignmentName(node) { export const HIGH_ENTROPY = "HIGH_ENTROPY"; export const PATTERN_MATCH = "PATTERN_MATCH"; + +export const FULL_TEXT_MATCH = "FULL_TEXT_MATCH"; diff --git a/staging/jsonc.eslintrc.js b/staging/jsonc.eslintrc.js index 76e710d..4497a85 100644 --- a/staging/jsonc.eslintrc.js +++ b/staging/jsonc.eslintrc.js @@ -1,11 +1,7 @@ module.exports = { - "extends": [ - "plugin:jsonc/base" - ], - "plugins": [ - "self" - ], - "rules": { - "self/no-secrets": "error" - } -} \ No newline at end of file + extends: ["plugin:jsonc/base"], + plugins: ["self"], + rules: { + "self/no-secrets": "error", + }, +}; diff --git a/staging/mixed.eslintrc.js b/staging/mixed.eslintrc.js index 4f53a02..e10beae 100644 --- a/staging/mixed.eslintrc.js +++ b/staging/mixed.eslintrc.js @@ -1,12 +1,8 @@ module.exports = { - env: { es6: true }, - "extends": [ - "plugin:jsonc/base" - ], - "plugins": [ - "self" - ], - "rules": { - "self/no-secrets": "error" - } -} \ No newline at end of file + env: { es6: true }, + extends: ["plugin:jsonc/base"], + plugins: ["self"], + rules: { + "self/no-secrets": "error", + }, +}; diff --git a/staging/normal.eslintrc.js b/staging/normal.eslintrc.js index 43d6282..54df6a0 100644 --- a/staging/normal.eslintrc.js +++ b/staging/normal.eslintrc.js @@ -1,9 +1,7 @@ module.exports = { - env: { es6: true }, - "plugins": [ - "self" - ], - "rules": { - "self/no-secrets": "error" - } -} \ No newline at end of file + env: { es6: true }, + plugins: ["self"], + rules: { + "self/no-secrets": "error", + }, +}; diff --git a/tests/lib/rules/no-pattern-match.ts b/tests/lib/rules/no-pattern-match.ts index 9950adb..722894e 100644 --- a/tests/lib/rules/no-pattern-match.ts +++ b/tests/lib/rules/no-pattern-match.ts @@ -1,30 +1,52 @@ import { rules } from "../../../src/index"; +import { FULL_TEXT_MATCH } from "../../../src/utils"; import RULE_TESTERS from "./rule-testers"; const noPatternMatch = rules["no-pattern-match"]; -const FULL_TEXT = ` +const FULL_TEXT_NO_SECRETS = ` +/**Not a problem**/ +const A = "Not a problem"; +`; + +const FULL_TEXT_SECRETS = ` /**SECRET**/ const VAULT = { token:"secret secret SECRET" }; `; +const patterns = { + Test: /secret/i, + MultiLine: /VAULT = {[\n.\s\t]*to/im, +}; + +const FULL_TEXT_MATCH_MSG = { + messageId: FULL_TEXT_MATCH, +}; + function createTests(_flatConfig = false) { return { valid: [ { - code: FULL_TEXT, + code: FULL_TEXT_NO_SECRETS, + options: [ + { + patterns, + }, + ], + }, + ], + invalid: [ + { + code: FULL_TEXT_SECRETS, options: [ { - additionalPatterns: { - Test: /secret/i, - MultiLine: /VAULT = {[\n.\s\t]*to/im, - }, + patterns, }, ], + errors: Array(5).fill(FULL_TEXT_MATCH_MSG), }, ], - invalid: [], }; }