diff --git a/lib/rules/boolean-prop-naming.js b/lib/rules/boolean-prop-naming.js index fe55d52065..e856a17967 100644 --- a/lib/rules/boolean-prop-naming.js +++ b/lib/rules/boolean-prop-naming.js @@ -18,6 +18,18 @@ const eslintUtil = require('../util/eslint'); const getSourceCode = eslintUtil.getSourceCode; const getText = eslintUtil.getText; +/** + * Checks if prop is nested + * @param {Object} prop Property object, single prop type declaration + * @returns {boolean} + */ +function nestedPropTypes(prop) { + return ( + prop.type === 'Property' + && prop.value.type === 'CallExpression' + ); +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -162,18 +174,6 @@ module.exports = { ); } - /** - * Checks if prop is nested - * @param {Object} prop Property object, single prop type declaration - * @returns {boolean} - */ - function nestedPropTypes(prop) { - return ( - prop.type === 'Property' - && prop.value.type === 'CallExpression' - ); - } - /** * Runs recursive check on all proptypes * @param {Array} proptypes A list of Property object (for each proptype defined) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 083a60a0e3..213e0ce3ca 100755 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -31,6 +31,108 @@ const OPTION_VALUES = [ ]; const DEFAULT_CONFIG = { props: OPTION_NEVER, children: OPTION_NEVER, propElementValues: OPTION_IGNORE }; +const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g; + +function containsLineTerminators(rawStringValue) { + return /[\n\r\u2028\u2029]/.test(rawStringValue); +} + +function containsBackslash(rawStringValue) { + return arrayIncludes(rawStringValue, '\\'); +} + +function containsHTMLEntity(rawStringValue) { + return HTML_ENTITY_REGEX().test(rawStringValue); +} + +function containsOnlyHtmlEntities(rawStringValue) { + return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === ''; +} + +function containsDisallowedJSXTextChars(rawStringValue) { + return /[{<>}]/.test(rawStringValue); +} + +function containsQuoteCharacters(value) { + return /['"]/.test(value); +} + +function containsMultilineComment(value) { + return /\/\*/.test(value); +} + +function escapeDoubleQuotes(rawStringValue) { + return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"'); +} + +function escapeBackslashes(rawStringValue) { + return rawStringValue.replace(/\\/g, '\\\\'); +} + +function needToEscapeCharacterForJSX(raw, node) { + return ( + containsBackslash(raw) + || containsHTMLEntity(raw) + || (node.parent.type !== 'JSXAttribute' && containsDisallowedJSXTextChars(raw)) + ); +} + +function containsWhitespaceExpression(child) { + if (child.type === 'JSXExpressionContainer') { + const value = child.expression.value; + return value ? jsxUtil.isWhiteSpaces(value) : false; + } + return false; +} + +function isLineBreak(text) { + return containsLineTerminators(text) && text.trim() === ''; +} + +function wrapNonHTMLEntities(text) { + const HTML_ENTITY = ''; + const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => ( + word === '' ? '' : `{${JSON.stringify(word)}}` + )).join(HTML_ENTITY); + + const htmlEntities = text.match(HTML_ENTITY_REGEX()); + return htmlEntities.reduce((acc, htmlEntity) => ( + acc.replace(HTML_ENTITY, htmlEntity) + ), withCurlyBraces); +} + +function wrapWithCurlyBraces(rawText) { + if (!containsLineTerminators(rawText)) { + return `{${JSON.stringify(rawText)}}`; + } + + return rawText.split('\n').map((line) => { + if (line.trim() === '') { + return line; + } + const firstCharIndex = line.search(/[^\s]/); + const leftWhitespace = line.slice(0, firstCharIndex); + const text = line.slice(firstCharIndex); + + if (containsHTMLEntity(line)) { + return `${leftWhitespace}${wrapNonHTMLEntities(text)}`; + } + return `${leftWhitespace}{${JSON.stringify(text)}}`; + }).join('\n'); +} + +function isWhiteSpaceLiteral(node) { + return node.type && node.type === 'Literal' && node.value && jsxUtil.isWhiteSpaces(node.value); +} + +function isStringWithTrailingWhiteSpaces(value) { + return /^\s|\s$/.test(value); +} + +function isLiteralWithTrailingWhiteSpaces(node) { + return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value); +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -74,100 +176,11 @@ module.exports = { }, create(context) { - const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g; const ruleOptions = context.options[0]; const userConfig = typeof ruleOptions === 'string' ? { props: ruleOptions, children: ruleOptions, propElementValues: OPTION_IGNORE } : Object.assign({}, DEFAULT_CONFIG, ruleOptions); - function containsLineTerminators(rawStringValue) { - return /[\n\r\u2028\u2029]/.test(rawStringValue); - } - - function containsBackslash(rawStringValue) { - return arrayIncludes(rawStringValue, '\\'); - } - - function containsHTMLEntity(rawStringValue) { - return HTML_ENTITY_REGEX().test(rawStringValue); - } - - function containsOnlyHtmlEntities(rawStringValue) { - return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === ''; - } - - function containsDisallowedJSXTextChars(rawStringValue) { - return /[{<>}]/.test(rawStringValue); - } - - function containsQuoteCharacters(value) { - return /['"]/.test(value); - } - - function containsMultilineComment(value) { - return /\/\*/.test(value); - } - - function escapeDoubleQuotes(rawStringValue) { - return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"'); - } - - function escapeBackslashes(rawStringValue) { - return rawStringValue.replace(/\\/g, '\\\\'); - } - - function needToEscapeCharacterForJSX(raw, node) { - return ( - containsBackslash(raw) - || containsHTMLEntity(raw) - || (node.parent.type !== 'JSXAttribute' && containsDisallowedJSXTextChars(raw)) - ); - } - - function containsWhitespaceExpression(child) { - if (child.type === 'JSXExpressionContainer') { - const value = child.expression.value; - return value ? jsxUtil.isWhiteSpaces(value) : false; - } - return false; - } - - function isLineBreak(text) { - return containsLineTerminators(text) && text.trim() === ''; - } - - function wrapNonHTMLEntities(text) { - const HTML_ENTITY = ''; - const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => ( - word === '' ? '' : `{${JSON.stringify(word)}}` - )).join(HTML_ENTITY); - - const htmlEntities = text.match(HTML_ENTITY_REGEX()); - return htmlEntities.reduce((acc, htmlEntity) => ( - acc.replace(HTML_ENTITY, htmlEntity) - ), withCurlyBraces); - } - - function wrapWithCurlyBraces(rawText) { - if (!containsLineTerminators(rawText)) { - return `{${JSON.stringify(rawText)}}`; - } - - return rawText.split('\n').map((line) => { - if (line.trim() === '') { - return line; - } - const firstCharIndex = line.search(/[^\s]/); - const leftWhitespace = line.slice(0, firstCharIndex); - const text = line.slice(firstCharIndex); - - if (containsHTMLEntity(line)) { - return `${leftWhitespace}${wrapNonHTMLEntities(text)}`; - } - return `${leftWhitespace}{${JSON.stringify(text)}}`; - }).join('\n'); - } - /** * Report and fix an unnecessary curly brace violation on a node * @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression @@ -233,18 +246,6 @@ module.exports = { }); } - function isWhiteSpaceLiteral(node) { - return node.type && node.type === 'Literal' && node.value && jsxUtil.isWhiteSpaces(node.value); - } - - function isStringWithTrailingWhiteSpaces(value) { - return /^\s|\s$/.test(value); - } - - function isLiteralWithTrailingWhiteSpaces(node) { - return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value); - } - // Bail out if there is any character that needs to be escaped in JSX // because escaping decreases readability and the original code may be more // readable anyway or intentional for other specific reasons diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js index 55a21881ff..09dcfb5b58 100644 --- a/lib/rules/no-unused-prop-types.js +++ b/lib/rules/no-unused-prop-types.js @@ -14,6 +14,15 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +/** + * Checks if the component must be validated + * @param {Object} component The component to process + * @returns {boolean} True if the component must be validated, false if not. + */ +function mustBeValidated(component) { + return !!component && !component.ignoreUnusedPropTypesValidation; +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -71,15 +80,6 @@ module.exports = { return configuration.ignore.indexOf(name) !== -1; } - /** - * Checks if the component must be validated - * @param {Object} component The component to process - * @returns {boolean} True if the component must be validated, false if not. - */ - function mustBeValidated(component) { - return !!component && !component.ignoreUnusedPropTypesValidation; - } - /** * Checks if a prop is used * @param {ASTNode} node The AST node being checked. diff --git a/lib/rules/self-closing-comp.js b/lib/rules/self-closing-comp.js index fe5fda4d49..23e168605f 100644 --- a/lib/rules/self-closing-comp.js +++ b/lib/rules/self-closing-comp.js @@ -9,12 +9,35 @@ const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); const report = require('../util/report'); +const optionDefaults = { component: true, html: true }; + +function isComponent(node) { + return ( + node.name + && (node.name.type === 'JSXIdentifier' || node.name.type === 'JSXMemberExpression') + && !jsxUtil.isDOMComponent(node) + ); +} + +function childrenIsEmpty(node) { + return node.parent.children.length === 0; +} + +function childrenIsMultilineSpaces(node) { + const childrens = node.parent.children; + + return ( + childrens.length === 1 + && (childrens[0].type === 'Literal' || childrens[0].type === 'JSXText') + && childrens[0].value.indexOf('\n') !== -1 + && childrens[0].value.replace(/(?!\xA0)\s/g, '') === '' + ); +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ -const optionDefaults = { component: true, html: true }; - const messages = { notSelfClosing: 'Empty components are self-closing', }; @@ -49,29 +72,6 @@ module.exports = { }, create(context) { - function isComponent(node) { - return ( - node.name - && (node.name.type === 'JSXIdentifier' || node.name.type === 'JSXMemberExpression') - && !jsxUtil.isDOMComponent(node) - ); - } - - function childrenIsEmpty(node) { - return node.parent.children.length === 0; - } - - function childrenIsMultilineSpaces(node) { - const childrens = node.parent.children; - - return ( - childrens.length === 1 - && (childrens[0].type === 'Literal' || childrens[0].type === 'JSXText') - && childrens[0].value.indexOf('\n') !== -1 - && childrens[0].value.replace(/(?!\xA0)\s/g, '') === '' - ); - } - function isShouldBeSelfClosed(node) { const configuration = Object.assign({}, optionDefaults, context.options[0]); return (