diff --git a/.eslintrc b/.eslintrc index 337ff4f35de17c..9f150c5d217e0c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -122,6 +122,7 @@ rules: align-multiline-assignment: 2 assert-fail-single-argument: 2 new-with-error: [2, Error, RangeError, TypeError, SyntaxError, ReferenceError] + no-useless-regex-char-class-escape: [2, { override: ['[', ']'] }] # Global scoped method and vars globals: diff --git a/lib/url.js b/lib/url.js index a199f4b581dcd2..5f87c0c36c43e5 100644 --- a/lib/url.js +++ b/lib/url.js @@ -44,7 +44,7 @@ const protocolPattern = /^([a-z0-9.+-]+:)/i; const portPattern = /:[0-9]*$/; // Special case for a simple path URL -const simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/; +const simplePathPattern = /^(\/\/?(?!\/)[^?\s]*)(\?[^\s]*)?$/; const hostnameMaxLen = 255; // protocols that can allow "unsafe" and "unwise" chars. diff --git a/tools/doc/html.js b/tools/doc/html.js index 3b60116b1ba422..16d96fb05f003b 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -90,7 +90,7 @@ function loadGtoc(cb) { function toID(filename) { return filename .replace('.html', '') - .replace(/[^\w\-]/g, '-') + .replace(/[^\w-]/g, '-') .replace(/-+/g, '-'); } @@ -284,7 +284,7 @@ function linkJsTypeDocs(text) { // Handle types, for example the source Markdown might say // "This argument should be a {Number} or {String}" for (i = 0; i < parts.length; i += 2) { - typeMatches = parts[i].match(/\{([^\}]+)\}/g); + typeMatches = parts[i].match(/\{([^}]+)\}/g); if (typeMatches) { typeMatches.forEach(function(typeMatch) { parts[i] = parts[i].replace(typeMatch, typeParser.toLink(typeMatch)); diff --git a/tools/doc/json.js b/tools/doc/json.js index a782c54028d756..f8210ef17fda5c 100644 --- a/tools/doc/json.js +++ b/tools/doc/json.js @@ -31,7 +31,7 @@ function doJSON(input, filename, cb) { // // This is for cases where the markdown semantic structure is lacking. if (type === 'paragraph' || type === 'html') { - var metaExpr = /\n*/g; + var metaExpr = /\n*/g; text = text.replace(metaExpr, function(_0, k, v) { current[k.trim()] = v.trim(); return ''; @@ -371,7 +371,7 @@ function parseListItem(item) { item.name = 'return'; text = text.replace(retExpr, ''); } else { - var nameExpr = /^['`"]?([^'`": \{]+)['`"]?\s*:?\s*/; + var nameExpr = /^['`"]?([^'`": {]+)['`"]?\s*:?\s*/; var name = text.match(nameExpr); if (name) { item.name = name[1]; @@ -388,7 +388,7 @@ function parseListItem(item) { } text = text.trim(); - var typeExpr = /^\{([^\}]+)\}/; + var typeExpr = /^\{([^}]+)\}/; var type = text.match(typeExpr); if (type) { item.type = type[1]; @@ -551,7 +551,7 @@ var classMethExpr = /^class\s*method\s*:?[^.]+\.([^ .()]+)\([^)]*\)\s*?$/i; var methExpr = /^(?:method:?\s*)?(?:[^.]+\.)?([^ .()]+)\([^)]*\)\s*?$/i; -var newExpr = /^new ([A-Z][a-zA-Z]+)\([^\)]*\)\s*?$/; +var newExpr = /^new ([A-Z][a-zA-Z]+)\([^)]*\)\s*?$/; var paramExpr = /\((.*)\);?$/; function newSection(tok) { diff --git a/tools/eslint-rules/no-useless-regex-char-class-escape.js b/tools/eslint-rules/no-useless-regex-char-class-escape.js new file mode 100644 index 00000000000000..934a3fa193b506 --- /dev/null +++ b/tools/eslint-rules/no-useless-regex-char-class-escape.js @@ -0,0 +1,190 @@ +/** + * @fileoverview Disallow useless escape in regex character class + * Based on 'no-useless-escape' rule + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const REGEX_CHARCLASS_ESCAPES = new Set('\\bcdDfnrsStvwWxu0123456789]'); + +/** + * Parses a regular expression into a list of regex character class list. + * @param {string} regExpText raw text used to create the regular expression + * @returns {Object[]} A list of character classes tokens with index and + * escape info + * @example + * + * parseRegExpCharClass('a\\b[cd-]') + * + * returns: + * [ + * { + * empty: false, + * start: 4, + * end: 6, + * chars: [ + * {text: 'c', index: 4, escaped: false}, + * {text: 'd', index: 5, escaped: false}, + * {text: '-', index: 6, escaped: false} + * ] + * } + * ] + */ + +function parseRegExpCharClass(regExpText) { + const charList = []; + let charListIdx = -1; + const initState = { + escapeNextChar: false, + inCharClass: false, + startingCharClass: false + }; + + regExpText.split('').reduce((state, char, index) => { + if (!state.escapeNextChar) { + if (char === '\\') { + return Object.assign(state, { escapeNextChar: true }); + } + if (char === '[' && !state.inCharClass) { + charListIdx += 1; + charList.push({ start: index + 1, chars: [], end: -1 }); + return Object.assign(state, { + inCharClass: true, + startingCharClass: true + }); + } + if (char === ']' && state.inCharClass) { + const charClass = charList[charListIdx]; + charClass.empty = charClass.chars.length === 0; + if (charClass.empty) { + charClass.start = charClass.end = -1; + } else { + charList[charListIdx].end = index - 1; + } + return Object.assign(state, { + inCharClass: false, + startingCharClass: false + }); + } + } + if (state.inCharClass) { + charList[charListIdx].chars.push({ + text: char, + index, escaped: + state.escapeNextChar + }); + } + return Object.assign(state, { + escapeNextChar: false, + startingCharClass: false + }); + }, initState); + + return charList; +} + +module.exports = { + meta: { + docs: { + description: 'disallow unnecessary regex characer class escape sequences', + category: 'Best Practices', + recommended: false + }, + fixable: 'code', + schema: [{ + 'type': 'object', + 'properties': { + 'override': { + 'type': 'array', + 'items': { 'type': 'string' }, + 'uniqueItems': true + } + }, + 'additionalProperties': false + }] + }, + + create(context) { + const overrideSet = new Set(context.options.length + ? context.options[0].override || [] + : []); + + /** + * Reports a node + * @param {ASTNode} node The node to report + * @param {number} startOffset The backslash's offset + * from the start of the node + * @param {string} character The uselessly escaped character + * (not including the backslash) + * @returns {void} + */ + function report(node, startOffset, character) { + context.report({ + node, + loc: { + line: node.loc.start.line, + column: node.loc.start.column + startOffset + }, + message: 'Unnecessary regex escape in character' + + ' class: \\{{character}}', + data: { character }, + fix: (fixer) => { + const start = node.range[0] + startOffset; + return fixer.replaceTextRange([start, start + 1], ''); + } + }); + } + + /** + * Checks if a node has superflous escape character + * in regex character class. + * + * @param {ASTNode} node - node to check. + * @returns {void} + */ + function check(node) { + if (node.regex) { + parseRegExpCharClass(node.regex.pattern) + .forEach((charClass) => { + charClass + .chars + // The '-' character is a special case if is not at + // either edge of the character class. To account for this, + // filter out '-' characters that appear in the middle of a + // character class. + .filter((charInfo) => !(charInfo.text === '-' && + (charInfo.index !== charClass.start && + charInfo.index !== charClass.end))) + + // The '^' character is a special case if it's at the beginning + // of the character class. To account for this, filter out '^' + // characters that appear at the start of a character class. + // + .filter((charInfo) => !(charInfo.text === '^' && + charInfo.index === charClass.start)) + + // Filter out characters that aren't escaped. + .filter((charInfo) => charInfo.escaped) + + // Filter out characters that are valid to escape, based on + // their position in the regular expression. + .filter((charInfo) => !REGEX_CHARCLASS_ESCAPES.has(charInfo.text)) + + // Filter out overridden character list. + .filter((charInfo) => !overrideSet.has(charInfo.text)) + + // Report all the remaining characters. + .forEach((charInfo) => + report(node, charInfo.index, charInfo.text)); + }); + } + } + + return { + Literal: check + }; + } +}; diff --git a/tools/license2rtf.js b/tools/license2rtf.js index 694e1c60501414..4b3d71fe5b99e9 100644 --- a/tools/license2rtf.js +++ b/tools/license2rtf.js @@ -122,7 +122,7 @@ function ParagraphParser() { // Detect separator "lines" within a block. These mark a paragraph break // and are stripped from the output. - if (/^\s*[=*\-]{5,}\s*$/.test(line)) { + if (/^\s*[=*-]{5,}\s*$/.test(line)) { flushParagraph(); return; } @@ -286,7 +286,7 @@ function RtfGenerator() { function rtfEscape(string) { return string - .replace(/[\\\{\}]/g, function(m) { + .replace(/[\\{}]/g, function(m) { return '\\' + m; }) .replace(/\t/g, function() {