Socket
Socket
Sign inDemoInstall

@stylistic/eslint-plugin-jsx

Package Overview
Dependencies
Maintainers
1
Versions
54
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stylistic/eslint-plugin-jsx - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

dist/configs.js

44

dist/index.js

@@ -21,30 +21,25 @@ 'use strict';

var jsxWrapMultilines = require('./jsx-wrap-multilines.js');
var configs = require('./configs.js');
require('./utils.js');
require('estraverse');
/**
* This file is GENERATED by scripts/prepare.ts
* DO NOT EDIT THIS FILE DIRECTLY
*/
var rules = {
'jsx-child-element-spacing': jsxChildElementSpacing.jsxChildElementSpacing,
'jsx-closing-bracket-location': jsxClosingBracketLocation.jsxClosingBracketLocation,
'jsx-closing-tag-location': jsxClosingTagLocation.jsxClosingTagLocation,
'jsx-curly-brace-presence': jsxCurlyBracePresence.jsxCurlyBracePresence,
'jsx-curly-newline': jsxCurlyNewline.jsxCurlyNewline,
'jsx-curly-spacing': jsxCurlySpacing.jsxCurlySpacing,
'jsx-equals-spacing': jsxEqualsSpacing.jsxEqualsSpacing,
'jsx-first-prop-new-line': jsxFirstPropNewLine.jsxFirstPropNewLine,
'jsx-indent': jsxIndent.jsxIndent,
'jsx-indent-props': jsxIndentProps.jsxIndentProps,
'jsx-max-props-per-line': jsxMaxPropsPerLine.jsxMaxPropsPerLine,
'jsx-newline': jsxNewline.jsxNewline,
'jsx-one-expression-per-line': jsxOneExpressionPerLine.jsxOneExpressionPerLine,
'jsx-props-no-multi-spaces': jsxPropsNoMultiSpaces.jsxPropsNoMultiSpaces,
'jsx-self-closing-comp': jsxSelfClosingComp.jsxSelfClosingComp,
'jsx-sort-props': jsxSortProps.jsxSortProps,
'jsx-tag-spacing': jsxTagSpacing.jsxTagSpacing,
'jsx-wrap-multilines': jsxWrapMultilines.jsxWrapMultilines,
"jsx-child-element-spacing": jsxChildElementSpacing.jsxChildElementSpacing,
"jsx-closing-bracket-location": jsxClosingBracketLocation.jsxClosingBracketLocation,
"jsx-closing-tag-location": jsxClosingTagLocation.jsxClosingTagLocation,
"jsx-curly-brace-presence": jsxCurlyBracePresence.jsxCurlyBracePresence,
"jsx-curly-newline": jsxCurlyNewline.jsxCurlyNewline,
"jsx-curly-spacing": jsxCurlySpacing.jsxCurlySpacing,
"jsx-equals-spacing": jsxEqualsSpacing.jsxEqualsSpacing,
"jsx-first-prop-new-line": jsxFirstPropNewLine.jsxFirstPropNewLine,
"jsx-indent": jsxIndent.jsxIndent,
"jsx-indent-props": jsxIndentProps.jsxIndentProps,
"jsx-max-props-per-line": jsxMaxPropsPerLine.jsxMaxPropsPerLine,
"jsx-newline": jsxNewline.jsxNewline,
"jsx-one-expression-per-line": jsxOneExpressionPerLine.jsxOneExpressionPerLine,
"jsx-props-no-multi-spaces": jsxPropsNoMultiSpaces.jsxPropsNoMultiSpaces,
"jsx-self-closing-comp": jsxSelfClosingComp.jsxSelfClosingComp,
"jsx-sort-props": jsxSortProps.jsxSortProps,
"jsx-tag-spacing": jsxTagSpacing.jsxTagSpacing,
"jsx-wrap-multilines": jsxWrapMultilines.jsxWrapMultilines
};

@@ -54,4 +49,5 @@

rules,
configs: configs.configs
};
module.exports = index;

@@ -5,56 +5,47 @@ 'use strict';

// This list is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
// Note: 'br' is not included because whitespace around br tags is inconsequential to the rendered output
const INLINE_ELEMENTS = new Set([
'a',
'abbr',
'acronym',
'b',
'bdo',
'big',
'button',
'cite',
'code',
'dfn',
'em',
'i',
'img',
'input',
'kbd',
'label',
'map',
'object',
'q',
'samp',
'script',
'select',
'small',
'span',
'strong',
'sub',
'sup',
'textarea',
'tt',
'var',
const INLINE_ELEMENTS = /* @__PURE__ */ new Set([
"a",
"abbr",
"acronym",
"b",
"bdo",
"big",
"button",
"cite",
"code",
"dfn",
"em",
"i",
"img",
"input",
"kbd",
"label",
"map",
"object",
"q",
"samp",
"script",
"select",
"small",
"span",
"strong",
"sub",
"sup",
"textarea",
"tt",
"var"
]);
const messages = {
spacingAfterPrev: 'Ambiguous spacing after previous element {{element}}',
spacingBeforeNext: 'Ambiguous spacing before next element {{element}}',
spacingAfterPrev: "Ambiguous spacing after previous element {{element}}",
spacingBeforeNext: "Ambiguous spacing before next element {{element}}"
};
var jsxChildElementSpacing = {
var jsxChildElementSpacing = utils.createRule({
meta: {
type: "layout",
docs: {
description: 'Enforce or disallow spaces inside of curly braces in JSX attributes and expressions',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-child-element-spacing'),
description: "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions",
url: utils.docsUrl("jsx-child-element-spacing")
},
fixable: null,
messages,
schema: [],
schema: []
},

@@ -64,42 +55,26 @@ create(context) {

const TEXT_PRECEDING_ELEMENT_PATTERN = /\S\s*\n\s*$/;
const elementName = node => (
node.openingElement
&& node.openingElement.name
&& node.openingElement.name.type === 'JSXIdentifier'
&& node.openingElement.name.name
);
const isInlineElement = node => (
node.type === 'JSXElement'
&& INLINE_ELEMENTS.has(elementName(node))
);
const elementName = (node) => node.openingElement && node.openingElement.name && node.openingElement.name.type === "JSXIdentifier" && node.openingElement.name.name || "";
const isInlineElement = (node) => node.type === "JSXElement" && INLINE_ELEMENTS.has(elementName(node));
const handleJSX = (node) => {
let lastChild = null;
let child = null;
(node.children.concat([null])).forEach((nextChild) => {
if (
(lastChild || nextChild)
&& (!lastChild || isInlineElement(lastChild))
&& (child && (child.type === 'Literal' || child.type === 'JSXText'))
&& (!nextChild || isInlineElement(nextChild))
&& true
) {
if (lastChild && child.value.match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
utils.report(context, messages.spacingAfterPrev, 'spacingAfterPrev', {
[...node.children, null].forEach((nextChild) => {
if ((lastChild || nextChild) && (!lastChild || isInlineElement(lastChild)) && (child && (child.type === "Literal" || child.type === "JSXText")) && (!nextChild || isInlineElement(nextChild)) && true) {
if (lastChild && String(child.value).match(TEXT_FOLLOWING_ELEMENT_PATTERN)) {
context.report({
messageId: "spacingAfterPrev",
node: lastChild,
loc: lastChild.loc.end,
data: {
element: elementName(lastChild),
},
element: elementName(lastChild)
}
});
}
else if (nextChild && child.value.match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
utils.report(context, messages.spacingBeforeNext, 'spacingBeforeNext', {
} else if (nextChild && String(child.value).match(TEXT_PRECEDING_ELEMENT_PATTERN)) {
context.report({
messageId: "spacingBeforeNext",
node: nextChild,
loc: nextChild.loc.start,
data: {
element: elementName(nextChild),
},
element: elementName(nextChild)
}
});

@@ -112,10 +87,9 @@ }

};
return {
JSXElement: handleJSX,
JSXFragment: handleJSX,
}
},
};
JSXFragment: handleJSX
};
}
});
exports.jsxChildElementSpacing = jsxChildElementSpacing;

@@ -5,166 +5,109 @@ 'use strict';

/**
* @fileoverview Validate closing bracket location in JSX
* @author Yannick Croissant
*/
const has = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
bracketLocation: 'The closing bracket must be {{location}}{{details}}',
bracketLocation: "The closing bracket must be {{location}}{{details}}"
};
var jsxClosingBracketLocation = {
meta: {
docs: {
description: 'Enforce closing bracket location in JSX',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-closing-bracket-location'),
description: "Enforce closing bracket location in JSX",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-closing-bracket-location")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
anyOf: [
{
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'],
enum: ["after-props", "props-aligned", "tag-aligned", "line-aligned"]
},
{
type: 'object',
type: "object",
properties: {
location: {
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned'],
},
enum: ["after-props", "props-aligned", "tag-aligned", "line-aligned"]
}
},
additionalProperties: false,
additionalProperties: false
},
{
type: 'object',
type: "object",
properties: {
nonEmpty: {
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false],
enum: ["after-props", "props-aligned", "tag-aligned", "line-aligned", false]
},
selfClosing: {
enum: ['after-props', 'props-aligned', 'tag-aligned', 'line-aligned', false],
},
enum: ["after-props", "props-aligned", "tag-aligned", "line-aligned", false]
}
},
additionalProperties: false,
},
],
}],
additionalProperties: false
}
]
}]
},
create(context) {
const MESSAGE_LOCATION = {
'after-props': 'placed after the last prop',
'after-tag': 'placed after the opening tag',
'props-aligned': 'aligned with the last prop',
'tag-aligned': 'aligned with the opening tag',
'line-aligned': 'aligned with the line containing the opening tag',
"after-props": "placed after the last prop",
"after-tag": "placed after the opening tag",
"props-aligned": "aligned with the last prop",
"tag-aligned": "aligned with the opening tag",
"line-aligned": "aligned with the line containing the opening tag"
};
const DEFAULT_LOCATION = 'tag-aligned';
const DEFAULT_LOCATION = "tag-aligned";
const config = context.options[0];
const options = {
nonEmpty: DEFAULT_LOCATION,
selfClosing: DEFAULT_LOCATION,
selfClosing: DEFAULT_LOCATION
};
if (typeof config === 'string') {
// simple shorthand [1, 'something']
if (typeof config === "string") {
options.nonEmpty = config;
options.selfClosing = config;
}
else if (typeof config === 'object') {
// [1, {location: 'something'}] (back-compat)
if (has(config, 'location')) {
} else if (typeof config === "object") {
if (has(config, "location")) {
options.nonEmpty = config.location;
options.selfClosing = config.location;
}
// [1, {nonEmpty: 'something'}]
if (has(config, 'nonEmpty'))
if (has(config, "nonEmpty"))
options.nonEmpty = config.nonEmpty;
// [1, {selfClosing: 'something'}]
if (has(config, 'selfClosing'))
if (has(config, "selfClosing"))
options.selfClosing = config.selfClosing;
}
/**
* Get expected location for the closing bracket
* @param {object} tokens Locations of the opening bracket, closing bracket and last prop
* @return {string} Expected location for the closing bracket
*/
function getExpectedLocation(tokens) {
let location;
// Is always after the opening tag if there is no props
if (typeof tokens.lastProp === 'undefined')
location = 'after-tag';
// Is always after the last prop if this one is on the same line as the opening bracket
if (typeof tokens.lastProp === "undefined")
location = "after-tag";
else if (tokens.opening.line === tokens.lastProp.lastLine)
location = 'after-props';
// Else use configuration dependent on selfClosing property
location = "after-props";
else
location = tokens.selfClosing ? options.selfClosing : options.nonEmpty;
return location
return location;
}
/**
* Get the correct 0-indexed column for the closing bracket, given the
* expected location.
* @param {object} tokens Locations of the opening bracket, closing bracket and last prop
* @param {string} expectedLocation Expected location for the closing bracket
* @return {?number} The correct column for the closing bracket, or null
*/
function getCorrectColumn(tokens, expectedLocation) {
switch (expectedLocation) {
case 'props-aligned':
return tokens.lastProp.column
case 'tag-aligned':
return tokens.opening.column
case 'line-aligned':
return tokens.openingStartOfLine.column
case "props-aligned":
return tokens.lastProp.column;
case "tag-aligned":
return tokens.opening.column;
case "line-aligned":
return tokens.openingStartOfLine.column;
default:
return null
return null;
}
}
/**
* Check if the closing bracket is correctly located
* @param {object} tokens Locations of the opening bracket, closing bracket and last prop
* @param {string} expectedLocation Expected location for the closing bracket
* @return {boolean} True if the closing bracket is correctly located, false if not
*/
function hasCorrectLocation(tokens, expectedLocation) {
switch (expectedLocation) {
case 'after-tag':
return tokens.tag.line === tokens.closing.line
case 'after-props':
return tokens.lastProp.lastLine === tokens.closing.line
case 'props-aligned':
case 'tag-aligned':
case 'line-aligned': {
case "after-tag":
return tokens.tag.line === tokens.closing.line;
case "after-props":
return tokens.lastProp.lastLine === tokens.closing.line;
case "props-aligned":
case "tag-aligned":
case "line-aligned": {
const correctColumn = getCorrectColumn(tokens, expectedLocation);
return correctColumn === tokens.closing.column
return correctColumn === tokens.closing.column;
}
default:
return true
return true;
}
}
/**
* Get the characters used for indentation on the line to be matched
* @param {object} tokens Locations of the opening bracket, closing bracket and last prop
* @param {string} expectedLocation Expected location for the closing bracket
* @param {number} [correctColumn] Expected column for the closing bracket. Default to 0
* @return {string} The characters used for indentation
*/
function getIndentation(tokens, expectedLocation, correctColumn) {

@@ -175,26 +118,17 @@ const newColumn = correctColumn || 0;

switch (expectedLocation) {
case 'props-aligned':
case "props-aligned":
indentation = /^\s*/.exec(context.getSourceCode().lines[tokens.lastProp.firstLine - 1])[0];
break
case 'tag-aligned':
case 'line-aligned':
break;
case "tag-aligned":
case "line-aligned":
indentation = /^\s*/.exec(context.getSourceCode().lines[tokens.opening.line - 1])[0];
break
break;
default:
indentation = '';
indentation = "";
}
if (indentation.length + 1 < newColumn) {
// Non-whitespace characters were included in the column offset
spaces = Array.from({ length: +correctColumn + 1 - indentation.length });
}
return indentation + spaces.join(' ')
return indentation + spaces.join(" ");
}
/**
* Get the locations of the opening bracket, closing bracket, last prop, and
* start of opening line.
* @param {ASTNode} node The node to check
* @return {object} Locations of the opening bracket, closing bracket, last
* prop and start of opening line.
*/
function getTokensLocations(node) {

@@ -211,3 +145,3 @@ const sourceCode = context.getSourceCode();

firstLine: sourceCode.getFirstToken(lastProp).loc.start.line,
lastLine: sourceCode.getLastToken(lastProp).loc.end.line,
lastLine: sourceCode.getLastToken(lastProp).loc.end.line
};

@@ -219,7 +153,7 @@ }

openTab: /^\t/.test(openingLine),
closeTab: /^\t/.test(closingLine),
closeTab: /^\t/.test(closingLine)
};
const openingStartOfLine = {
column: /^\s*/.exec(openingLine)[0].length,
line: opening.line,
line: opening.line
};

@@ -233,18 +167,9 @@ return {

selfClosing: node.selfClosing,
openingStartOfLine,
}
openingStartOfLine
};
}
/**
* Get an unique ID for a given JSXOpeningElement
*
* @param {ASTNode} node The AST node being checked.
* @returns {string} Unique ID (based on its range)
*/
function getOpeningElementId(node) {
return node.range.join(':')
return node.range.join(":");
}
const lastAttributeNode = {};
return {

@@ -254,11 +179,8 @@ JSXAttribute(node) {

},
JSXSpreadAttribute(node) {
lastAttributeNode[getOpeningElementId(node.parent)] = node;
},
'JSXOpeningElement:exit': function (node) {
"JSXOpeningElement:exit": function(node) {
const attributeNode = lastAttributeNode[getOpeningElementId(node)];
const cachedLastAttributeEndPos = attributeNode ? attributeNode.range[1] : null;
let expectedNextLine;

@@ -268,19 +190,13 @@ const tokens = getTokensLocations(node);

let usingSameIndentation = true;
if (expectedLocation === 'tag-aligned')
if (expectedLocation === "tag-aligned")
usingSameIndentation = tokens.isTab.openTab === tokens.isTab.closeTab;
if (hasCorrectLocation(tokens, expectedLocation) && usingSameIndentation)
return
return;
const data = { location: MESSAGE_LOCATION[expectedLocation] };
const correctColumn = getCorrectColumn(tokens, expectedLocation);
if (correctColumn !== null) {
expectedNextLine = tokens.lastProp
&& (tokens.lastProp.lastLine === tokens.closing.line);
data.details = ` (expected column ${correctColumn + 1}${expectedNextLine ? ' on the next line)' : ')'}`;
expectedNextLine = tokens.lastProp && tokens.lastProp.lastLine === tokens.closing.line;
data.details = ` (expected column ${correctColumn + 1}${expectedNextLine ? " on the next line)" : ")"}`;
}
utils.report(context, messages.bracketLocation, 'bracketLocation', {
utils.report(context, messages.bracketLocation, "bracketLocation", {
node,

@@ -290,25 +206,25 @@ loc: tokens.closing,

fix(fixer) {
const closingTag = tokens.selfClosing ? '/>' : '>';
const closingTag = tokens.selfClosing ? "/>" : ">";
switch (expectedLocation) {
case 'after-tag':
case "after-tag":
if (cachedLastAttributeEndPos)
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], (expectedNextLine ? '\n' : '') + closingTag)
return fixer.replaceTextRange([node.name.range[1], node.range[1]], (expectedNextLine ? '\n' : ' ') + closingTag)
case 'after-props':
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], (expectedNextLine ? '\n' : '') + closingTag)
case 'props-aligned':
case 'tag-aligned':
case 'line-aligned':
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], `\n${getIndentation(tokens, expectedLocation, correctColumn)}${closingTag}`)
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], (expectedNextLine ? "\n" : "") + closingTag);
return fixer.replaceTextRange([node.name.range[1], node.range[1]], (expectedNextLine ? "\n" : " ") + closingTag);
case "after-props":
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], (expectedNextLine ? "\n" : "") + closingTag);
case "props-aligned":
case "tag-aligned":
case "line-aligned":
return fixer.replaceTextRange([cachedLastAttributeEndPos, node.range[1]], `
${getIndentation(tokens, expectedLocation, correctColumn)}${closingTag}`);
default:
return true
return true;
}
},
}
});
},
}
},
}
};
}
};
exports.jsxClosingBracketLocation = jsxClosingBracketLocation;

@@ -5,44 +5,26 @@ 'use strict';

/**
* @fileoverview Validate closing tag location in JSX
* @author Ross Solomon
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
onOwnLine: 'Closing tag of a multiline JSX expression must be on its own line.',
matchIndent: 'Expected closing tag to match indentation of opening.',
onOwnLine: "Closing tag of a multiline JSX expression must be on its own line.",
matchIndent: "Expected closing tag to match indentation of opening."
};
var jsxClosingTagLocation = {
meta: {
docs: {
description: 'Enforce closing tag location for multiline JSX',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-closing-tag-location'),
description: "Enforce closing tag location for multiline JSX",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-closing-tag-location")
},
fixable: 'whitespace',
messages,
fixable: "whitespace",
messages
},
create(context) {
function handleClosingElement(node) {
if (!node.parent)
return
return;
const opening = node.parent.openingElement || node.parent.openingFragment;
if (opening.loc.start.line === node.loc.start.line)
return
return;
if (opening.loc.start.column === node.loc.start.column)
return
const messageId = utils.isNodeFirstInLine(context, node)
? 'matchIndent'
: 'onOwnLine';
return;
const messageId = utils.isNodeFirstInLine(context, node) ? "matchIndent" : "onOwnLine";
utils.report(context, messages[messageId], messageId, {

@@ -52,22 +34,21 @@ node,

fix(fixer) {
const indent = Array(opening.loc.start.column + 1).join(' ');
const indent = Array(opening.loc.start.column + 1).join(" ");
if (utils.isNodeFirstInLine(context, node)) {
return fixer.replaceTextRange(
[node.range[0] - node.loc.start.column, node.range[0]],
indent,
)
indent
);
}
return fixer.insertTextBefore(node, `\n${indent}`)
},
return fixer.insertTextBefore(node, `
${indent}`);
}
});
}
return {
JSXClosingElement: handleClosingElement,
JSXClosingFragment: handleClosingElement,
}
},
JSXClosingFragment: handleClosingElement
};
}
};
exports.jsxClosingTagLocation = jsxClosingTagLocation;

@@ -5,47 +5,25 @@ 'use strict';

/**
* @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX
* @author Jacky Ho
* @author Simon Lydell
*/
const arrayIncludes = (arr, value) => arr.includes(value);
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const OPTION_ALWAYS = 'always';
const OPTION_NEVER = 'never';
const OPTION_IGNORE = 'ignore';
const OPTION_ALWAYS = "always";
const OPTION_NEVER = "never";
const OPTION_IGNORE = "ignore";
const OPTION_VALUES = [
OPTION_ALWAYS,
OPTION_NEVER,
OPTION_IGNORE,
OPTION_IGNORE
];
const DEFAULT_CONFIG = { props: OPTION_NEVER, children: OPTION_NEVER, propElementValues: OPTION_IGNORE };
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
unnecessaryCurly: 'Curly braces are unnecessary here.',
missingCurly: 'Need to wrap this literal in a JSX expression.',
unnecessaryCurly: "Curly braces are unnecessary here.",
missingCurly: "Need to wrap this literal in a JSX expression."
};
var jsxCurlyBracePresence = {
meta: {
docs: {
description: 'Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-curly-brace-presence'),
description: "Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-curly-brace-presence")
},
fixable: 'code',
fixable: "code",
messages,
schema: [

@@ -55,121 +33,86 @@ {

{
type: 'object',
type: "object",
properties: {
props: { enum: OPTION_VALUES },
children: { enum: OPTION_VALUES },
propElementValues: { enum: OPTION_VALUES },
propElementValues: { enum: OPTION_VALUES }
},
additionalProperties: false,
additionalProperties: false
},
{
enum: OPTION_VALUES,
},
],
},
],
enum: OPTION_VALUES
}
]
}
]
},
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);
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)
return /[\n\r\u2028\u2029]/.test(rawStringValue);
}
function containsBackslash(rawStringValue) {
return arrayIncludes(rawStringValue, '\\')
return arrayIncludes(rawStringValue, "\\");
}
function containsHTMLEntity(rawStringValue) {
return HTML_ENTITY_REGEX().test(rawStringValue)
return HTML_ENTITY_REGEX().test(rawStringValue);
}
function containsOnlyHtmlEntities(rawStringValue) {
return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === ''
return rawStringValue.replace(HTML_ENTITY_REGEX(), "").trim() === "";
}
function containsDisallowedJSXTextChars(rawStringValue) {
return /[{<>}]/.test(rawStringValue)
return /[{<>}]/.test(rawStringValue);
}
function containsQuoteCharacters(value) {
return /['"]/.test(value)
return /['"]/.test(value);
}
function containsMultilineComment(value) {
return /\/\*/.test(value)
return /\/\*/.test(value);
}
function escapeDoubleQuotes(rawStringValue) {
return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"')
return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"');
}
function escapeBackslashes(rawStringValue) {
return rawStringValue.replace(/\\/g, '\\\\')
return rawStringValue.replace(/\\/g, "\\\\");
}
function needToEscapeCharacterForJSX(raw, node) {
return (
containsBackslash(raw)
|| containsHTMLEntity(raw)
|| (node.parent.type !== 'JSXAttribute' && containsDisallowedJSXTextChars(raw))
)
return containsBackslash(raw) || containsHTMLEntity(raw) || node.parent.type !== "JSXAttribute" && containsDisallowedJSXTextChars(raw);
}
function containsWhitespaceExpression(child) {
if (child.type === 'JSXExpressionContainer') {
if (child.type === "JSXExpressionContainer") {
const value = child.expression.value;
return value ? utils.isWhiteSpaces(value) : false
return value ? utils.isWhiteSpaces(value) : false;
}
return false
return false;
}
function isLineBreak(text) {
return containsLineTerminators(text) && text.trim() === ''
return containsLineTerminators(text) && text.trim() === "";
}
function wrapNonHTMLEntities(text) {
const HTML_ENTITY = '<HTML_ENTITY>';
const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map(word => (
word === '' ? '' : `{${JSON.stringify(word)}}`
)).join(HTML_ENTITY);
const HTML_ENTITY = "<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)
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
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')
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
*/
function reportUnnecessaryCurly(JSXExpressionNode) {
utils.report(context, messages.unnecessaryCurly, 'unnecessaryCurly', {
utils.report(context, messages.unnecessaryCurly, "unnecessaryCurly", {
node: JSXExpressionNode,
fix(fixer) {
const expression = JSXExpressionNode.expression;
let textToReplace;

@@ -179,103 +122,51 @@ if (utils.isJSX(expression)) {

textToReplace = sourceCode.getText(expression);
}
else {
} else {
const expressionType = expression && expression.type;
const parentType = JSXExpressionNode.parent.type;
if (parentType === 'JSXAttribute') {
textToReplace = `"${expressionType === 'TemplateLiteral'
? expression.quasis[0].value.raw
: expression.raw.slice(1, -1)
}"`;
}
else if (utils.isJSX(expression)) {
if (parentType === "JSXAttribute") {
textToReplace = `"${expressionType === "TemplateLiteral" ? expression.quasis[0].value.raw : expression.raw.slice(1, -1)}"`;
} else if (utils.isJSX(expression)) {
const sourceCode = context.getSourceCode();
textToReplace = sourceCode.getText(expression);
} else {
textToReplace = expressionType === "TemplateLiteral" ? expression.quasis[0].value.cooked : expression.value;
}
else {
textToReplace = expressionType === 'TemplateLiteral'
? expression.quasis[0].value.cooked : expression.value;
}
}
return fixer.replaceText(JSXExpressionNode, textToReplace)
},
return fixer.replaceText(JSXExpressionNode, textToReplace);
}
});
}
function reportMissingCurly(literalNode) {
utils.report(context, messages.missingCurly, 'missingCurly', {
utils.report(context, messages.missingCurly, "missingCurly", {
node: literalNode,
fix(fixer) {
if (utils.isJSX(literalNode))
return fixer.replaceText(literalNode, `{${context.getSourceCode().getText(literalNode)}}`)
// If a HTML entity name is found, bail out because it can be fixed
// by either using the real character or the unicode equivalent.
// If it contains any line terminator character, bail out as well.
if (
containsOnlyHtmlEntities(literalNode.raw)
|| (literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw))
|| isLineBreak(literalNode.raw)
)
return null
const expression = literalNode.parent.type === 'JSXAttribute'
? `{"${escapeDoubleQuotes(escapeBackslashes(
literalNode.raw.slice(1, -1),
))}"}`
: wrapWithCurlyBraces(literalNode.raw);
return fixer.replaceText(literalNode, expression)
},
return fixer.replaceText(literalNode, `{${context.getSourceCode().getText(literalNode)}}`);
if (containsOnlyHtmlEntities(literalNode.raw) || literalNode.parent.type === "JSXAttribute" && containsLineTerminators(literalNode.raw) || isLineBreak(literalNode.raw))
return null;
const expression = literalNode.parent.type === "JSXAttribute" ? `{"${escapeDoubleQuotes(escapeBackslashes(
literalNode.raw.slice(1, -1)
))}"}` : wrapWithCurlyBraces(literalNode.raw);
return fixer.replaceText(literalNode, expression);
}
});
}
function isWhiteSpaceLiteral(node) {
return node.type && node.type === 'Literal' && node.value && utils.isWhiteSpaces(node.value)
return node.type && node.type === "Literal" && node.value && utils.isWhiteSpaces(node.value);
}
function isStringWithTrailingWhiteSpaces(value) {
return /^\s|\s$/.test(value)
return /^\s|\s$/.test(value);
}
function isLiteralWithTrailingWhiteSpaces(node) {
return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value)
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
function lintUnnecessaryCurly(JSXExpressionNode) {
const expression = JSXExpressionNode.expression;
const expressionType = expression.type;
const sourceCode = context.getSourceCode();
// Curly braces containing comments are necessary
if (sourceCode.getCommentsInside && sourceCode.getCommentsInside(JSXExpressionNode).length > 0)
return
if (
(expressionType === 'Literal' || expressionType === 'JSXText')
&& typeof expression.value === 'string'
&& (
(JSXExpressionNode.parent.type === 'JSXAttribute' && !isWhiteSpaceLiteral(expression))
|| !isLiteralWithTrailingWhiteSpaces(expression)
)
&& !containsMultilineComment(expression.value)
&& !needToEscapeCharacterForJSX(expression.raw, JSXExpressionNode) && (
utils.isJSX(JSXExpressionNode.parent)
|| !containsQuoteCharacters(expression.value)
)
)
return;
if ((expressionType === "Literal" || expressionType === "JSXText") && typeof expression.value === "string" && (JSXExpressionNode.parent.type === "JSXAttribute" && !isWhiteSpaceLiteral(expression) || !isLiteralWithTrailingWhiteSpaces(expression)) && !containsMultilineComment(expression.value) && !needToEscapeCharacterForJSX(expression.raw, JSXExpressionNode) && (utils.isJSX(JSXExpressionNode.parent) || !containsQuoteCharacters(expression.value)))
reportUnnecessaryCurly(JSXExpressionNode);
else if (
expressionType === 'TemplateLiteral'
&& expression.expressions.length === 0
&& !expression.quasis[0].value.raw.includes('\n')
&& !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw)
&& !needToEscapeCharacterForJSX(expression.quasis[0].value.raw, JSXExpressionNode)
&& !containsQuoteCharacters(expression.quasis[0].value.cooked)
)
else if (expressionType === "TemplateLiteral" && expression.expressions.length === 0 && !expression.quasis[0].value.raw.includes("\n") && !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw) && !needToEscapeCharacterForJSX(expression.quasis[0].value.raw, JSXExpressionNode) && !containsQuoteCharacters(expression.quasis[0].value.cooked))
reportUnnecessaryCurly(JSXExpressionNode);

@@ -285,15 +176,5 @@ else if (utils.isJSX(expression))

}
function areRuleConditionsSatisfied(parent, config, ruleCondition) {
return (
parent.type === 'JSXAttribute'
&& typeof config.props === 'string'
&& config.props === ruleCondition
) || (
utils.isJSX(parent)
&& typeof config.children === 'string'
&& config.children === ruleCondition
)
return parent.type === "JSXAttribute" && typeof config.props === "string" && config.props === ruleCondition || utils.isJSX(parent) && typeof config.children === "string" && config.children === ruleCondition;
}
function getAdjacentSiblings(node, children) {

@@ -303,95 +184,51 @@ for (let i = 1; i < children.length - 1; i++) {

if (node === child)
return [children[i - 1], children[i + 1]]
return [children[i - 1], children[i + 1]];
}
if (node === children[0] && children[1])
return [children[1]]
return [children[1]];
if (node === children[children.length - 1] && children[children.length - 2])
return [children[children.length - 2]]
return []
return [children[children.length - 2]];
return [];
}
function hasAdjacentJsxExpressionContainers(node, children) {
if (!children)
return false
const childrenExcludingWhitespaceLiteral = children.filter(child => !isWhiteSpaceLiteral(child));
return false;
const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
return adjSiblings.some(x => x.type && x.type === 'JSXExpressionContainer')
return adjSiblings.some((x) => x.type && x.type === "JSXExpressionContainer");
}
function hasAdjacentJsx(node, children) {
if (!children)
return false
const childrenExcludingWhitespaceLiteral = children.filter(child => !isWhiteSpaceLiteral(child));
return false;
const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
return adjSiblings.some(x => x.type && arrayIncludes(['JSXExpressionContainer', 'JSXElement'], x.type))
return adjSiblings.some((x) => x.type && arrayIncludes(["JSXExpressionContainer", "JSXElement"], x.type));
}
function shouldCheckForUnnecessaryCurly(node, config) {
const parent = node.parent;
// Bail out if the parent is a JSXAttribute & its contents aren't
// StringLiteral or TemplateLiteral since e.g
// <App prop1={<CustomEl />} prop2={<CustomEl>...</CustomEl>} />
if (
parent.type && parent.type === 'JSXAttribute'
&& (node.expression && node.expression.type
&& node.expression.type !== 'Literal'
&& node.expression.type !== 'StringLiteral'
&& node.expression.type !== 'TemplateLiteral')
)
return false
// If there are adjacent `JsxExpressionContainer` then there is no need,
// to check for unnecessary curly braces.
if (parent.type && parent.type === "JSXAttribute" && (node.expression && node.expression.type && node.expression.type !== "Literal" && node.expression.type !== "StringLiteral" && node.expression.type !== "TemplateLiteral"))
return false;
if (utils.isJSX(parent) && hasAdjacentJsxExpressionContainers(node, parent.children))
return false
return false;
if (containsWhitespaceExpression(node) && hasAdjacentJsx(node, parent.children))
return false
if (
parent.children
&& parent.children.length === 1
&& containsWhitespaceExpression(node)
)
return false
return areRuleConditionsSatisfied(parent, config, OPTION_NEVER)
return false;
if (parent.children && parent.children.length === 1 && containsWhitespaceExpression(node))
return false;
return areRuleConditionsSatisfied(parent, config, OPTION_NEVER);
}
function shouldCheckForMissingCurly(node, config) {
if (utils.isJSX(node))
return config.propElementValues !== OPTION_IGNORE
if (
isLineBreak(node.raw)
|| containsOnlyHtmlEntities(node.raw)
)
return false
return config.propElementValues !== OPTION_IGNORE;
if (isLineBreak(node.raw) || containsOnlyHtmlEntities(node.raw))
return false;
const parent = node.parent;
if (
parent.children
&& parent.children.length === 1
&& containsWhitespaceExpression(parent.children[0])
)
return false
return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS)
if (parent.children && parent.children.length === 1 && containsWhitespaceExpression(parent.children[0]))
return false;
return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS);
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
'JSXAttribute > JSXExpressionContainer > JSXElement': function (node) {
"JSXAttribute > JSXExpressionContainer > JSXElement": function(node) {
if (userConfig.propElementValues === OPTION_NEVER)
reportUnnecessaryCurly(node.parent);
},
JSXExpressionContainer(node) {

@@ -401,11 +238,10 @@ if (shouldCheckForUnnecessaryCurly(node, userConfig))

},
'JSXAttribute > JSXElement, Literal, JSXText': function (node) {
"JSXAttribute > JSXElement, Literal, JSXText": function(node) {
if (shouldCheckForMissingCurly(node, userConfig))
reportMissingCurly(node);
},
}
},
}
};
}
};
exports.jsxCurlyBracePresence = jsxCurlyBracePresence;

@@ -5,54 +5,36 @@ 'use strict';

/**
* @fileoverview enforce consistent line breaks inside jsx curly
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function getNormalizedOption(context) {
const rawOption = context.options[0] || 'consistent';
if (rawOption === 'consistent') {
const rawOption = context.options[0] || "consistent";
if (rawOption === "consistent") {
return {
multiline: 'consistent',
singleline: 'consistent',
}
multiline: "consistent",
singleline: "consistent"
};
}
if (rawOption === 'never') {
if (rawOption === "never") {
return {
multiline: 'forbid',
singleline: 'forbid',
}
multiline: "forbid",
singleline: "forbid"
};
}
return {
multiline: rawOption.multiline || 'consistent',
singleline: rawOption.singleline || 'consistent',
}
multiline: rawOption.multiline || "consistent",
singleline: rawOption.singleline || "consistent"
};
}
const messages = {
expectedBefore: 'Expected newline before \'}\'.',
expectedAfter: 'Expected newline after \'{\'.',
unexpectedBefore: 'Unexpected newline before \'}\'.',
unexpectedAfter: 'Unexpected newline after \'{\'.',
expectedBefore: "Expected newline before '}'.",
expectedAfter: "Expected newline after '{'.",
unexpectedBefore: "Unexpected newline before '}'.",
unexpectedAfter: "Unexpected newline after '{'."
};
var jsxCurlyNewline = {
meta: {
type: 'layout',
type: "layout",
docs: {
description: 'Enforce consistent linebreaks in curly braces in JSX attributes and expressions',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-curly-newline'),
description: "Enforce consistent linebreaks in curly braces in JSX attributes and expressions",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-curly-newline")
},
fixable: 'whitespace',
fixable: "whitespace",
schema: [

@@ -62,60 +44,35 @@ {

{
enum: ['consistent', 'never'],
enum: ["consistent", "never"]
},
{
type: 'object',
type: "object",
properties: {
singleline: { enum: ['consistent', 'require', 'forbid'] },
multiline: { enum: ['consistent', 'require', 'forbid'] },
singleline: { enum: ["consistent", "require", "forbid"] },
multiline: { enum: ["consistent", "require", "forbid"] }
},
additionalProperties: false,
},
],
},
additionalProperties: false
}
]
}
],
messages,
messages
},
create(context) {
const sourceCode = context.getSourceCode();
const option = getNormalizedOption(context);
// ----------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------
/**
* Determines whether two adjacent tokens are on the same line.
* @param {object} left - The left token object.
* @param {object} right - The right token object.
* @returns {boolean} Whether or not the tokens are on the same line.
*/
function isTokenOnSameLine(left, right) {
return left.loc.end.line === right.loc.start.line
return left.loc.end.line === right.loc.start.line;
}
/**
* Determines whether there should be newlines inside curlys
* @param {ASTNode} expression The expression contained in the curlys
* @param {boolean} hasLeftNewline `true` if the left curly has a newline in the current code.
* @returns {boolean} `true` if there should be newlines inside the function curlys
*/
function shouldHaveNewlines(expression, hasLeftNewline) {
const isMultiline = expression.loc.start.line !== expression.loc.end.line;
switch (isMultiline ? option.multiline : option.singleline) {
case 'forbid': return false
case 'require': return true
case 'consistent':
default: return hasLeftNewline
case "forbid":
return false;
case "require":
return true;
case "consistent":
default:
return hasLeftNewline;
}
}
/**
* Validates curlys
* @param {object} curlys An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
* @param {ASTNode} expression The expression inside the curly
* @returns {void}
*/
function validateCurlys(curlys, expression) {

@@ -129,51 +86,32 @@ const leftCurly = curlys.leftCurly;

const needsNewlines = shouldHaveNewlines(expression, hasLeftNewline);
if (hasLeftNewline && !needsNewlines) {
utils.report(context, messages.unexpectedAfter, 'unexpectedAfter', {
utils.report(context, messages.unexpectedAfter, "unexpectedAfter", {
node: leftCurly,
fix(fixer) {
return sourceCode
.getText()
.slice(leftCurly.range[1], tokenAfterLeftCurly.range[0])
.trim()
? null // If there is a comment between the { and the first element, don't do a fix.
: fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]])
},
return sourceCode.getText().slice(leftCurly.range[1], tokenAfterLeftCurly.range[0]).trim() ? null : fixer.removeRange([leftCurly.range[1], tokenAfterLeftCurly.range[0]]);
}
});
}
else if (!hasLeftNewline && needsNewlines) {
utils.report(context, messages.expectedAfter, 'expectedAfter', {
} else if (!hasLeftNewline && needsNewlines) {
utils.report(context, messages.expectedAfter, "expectedAfter", {
node: leftCurly,
fix: fixer => fixer.insertTextAfter(leftCurly, '\n'),
fix: (fixer) => fixer.insertTextAfter(leftCurly, "\n")
});
}
if (hasRightNewline && !needsNewlines) {
utils.report(context, messages.unexpectedBefore, 'unexpectedBefore', {
utils.report(context, messages.unexpectedBefore, "unexpectedBefore", {
node: rightCurly,
fix(fixer) {
return sourceCode
.getText()
.slice(tokenBeforeRightCurly.range[1], rightCurly.range[0])
.trim()
? null // If there is a comment between the last element and the }, don't do a fix.
: fixer.removeRange([
tokenBeforeRightCurly.range[1],
rightCurly.range[0],
])
},
return sourceCode.getText().slice(tokenBeforeRightCurly.range[1], rightCurly.range[0]).trim() ? null : fixer.removeRange([
tokenBeforeRightCurly.range[1],
rightCurly.range[0]
]);
}
});
}
else if (!hasRightNewline && needsNewlines) {
utils.report(context, messages.expectedBefore, 'expectedBefore', {
} else if (!hasRightNewline && needsNewlines) {
utils.report(context, messages.expectedBefore, "expectedBefore", {
node: rightCurly,
fix: fixer => fixer.insertTextBefore(rightCurly, '\n'),
fix: (fixer) => fixer.insertTextBefore(rightCurly, "\n")
});
}
}
// ----------------------------------------------------------------------
// Public
// ----------------------------------------------------------------------
return {

@@ -183,10 +121,10 @@ JSXExpressionContainer(node) {

leftCurly: sourceCode.getFirstToken(node),
rightCurly: sourceCode.getLastToken(node),
rightCurly: sourceCode.getLastToken(node)
};
validateCurlys(curlyTokens, node.expression);
},
}
},
}
};
}
};
exports.jsxCurlyNewline = jsxCurlyNewline;

@@ -5,115 +5,92 @@ 'use strict';

/**
* @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes.
* @author Jamund Ferguson
* @author Brandyn Bennett
* @author Michael Ficarra
* @author Vignesh Anand
* @author Jamund Ferguson
* @author Yannick Croissant
* @author Erik Wendel
*/
const has = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const SPACING = {
always: 'always',
never: 'never',
always: "always",
never: "never"
};
const SPACING_VALUES = [SPACING.always, SPACING.never];
const messages = {
noNewlineAfter: 'There should be no newline after \'{{token}}\'',
noNewlineBefore: 'There should be no newline before \'{{token}}\'',
noSpaceAfter: 'There should be no space after \'{{token}}\'',
noSpaceBefore: 'There should be no space before \'{{token}}\'',
spaceNeededAfter: 'A space is required after \'{{token}}\'',
spaceNeededBefore: 'A space is required before \'{{token}}\'',
noNewlineAfter: "There should be no newline after '{{token}}'",
noNewlineBefore: "There should be no newline before '{{token}}'",
noSpaceAfter: "There should be no space after '{{token}}'",
noSpaceBefore: "There should be no space before '{{token}}'",
spaceNeededAfter: "A space is required after '{{token}}'",
spaceNeededBefore: "A space is required before '{{token}}'"
};
var jsxCurlySpacing = {
meta: {
docs: {
description: 'Enforce or disallow spaces inside of curly braces in JSX attributes and expressions',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-curly-spacing'),
description: "Enforce or disallow spaces inside of curly braces in JSX attributes and expressions",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-curly-spacing")
},
fixable: 'code',
fixable: "code",
messages,
schema: {
definitions: {
basicConfig: {
type: 'object',
type: "object",
properties: {
when: {
enum: SPACING_VALUES,
enum: SPACING_VALUES
},
allowMultiline: {
type: 'boolean',
type: "boolean"
},
spacing: {
type: 'object',
type: "object",
properties: {
objectLiterals: {
enum: SPACING_VALUES,
},
},
},
},
enum: SPACING_VALUES
}
}
}
}
},
basicConfigOrBoolean: {
anyOf: [{
$ref: '#/definitions/basicConfig',
$ref: "#/definitions/basicConfig"
}, {
type: 'boolean',
}],
},
type: "boolean"
}]
}
},
type: 'array',
type: "array",
items: [{
anyOf: [{
allOf: [{
$ref: '#/definitions/basicConfig',
$ref: "#/definitions/basicConfig"
}, {
type: 'object',
type: "object",
properties: {
attributes: {
$ref: '#/definitions/basicConfigOrBoolean',
$ref: "#/definitions/basicConfigOrBoolean"
},
children: {
$ref: '#/definitions/basicConfigOrBoolean',
},
},
}],
$ref: "#/definitions/basicConfigOrBoolean"
}
}
}]
}, {
enum: SPACING_VALUES,
}],
enum: SPACING_VALUES
}]
}, {
type: 'object',
type: "object",
properties: {
allowMultiline: {
type: 'boolean',
type: "boolean"
},
spacing: {
type: 'object',
type: "object",
properties: {
objectLiterals: {
enum: SPACING_VALUES,
},
},
},
enum: SPACING_VALUES
}
}
}
},
additionalProperties: false,
}],
},
additionalProperties: false
}]
}
},
create(context) {

@@ -123,17 +100,14 @@ function normalizeConfig(configOrTrue, defaults, lastPass) {

const when = config.when || defaults.when;
const allowMultiline = has(config, 'allowMultiline') ? config.allowMultiline : defaults.allowMultiline;
const allowMultiline = has(config, "allowMultiline") ? config.allowMultiline : defaults.allowMultiline;
const spacing = config.spacing || {};
let objectLiteralSpaces = spacing.objectLiterals || defaults.objectLiteralSpaces;
if (lastPass) {
// On the final pass assign the values that should be derived from others if they are still undefined
objectLiteralSpaces = objectLiteralSpaces || when;
}
return {
when,
allowMultiline,
objectLiteralSpaces,
}
objectLiteralSpaces
};
}
const DEFAULT_WHEN = SPACING.never;

@@ -143,109 +117,62 @@ const DEFAULT_ALLOW_MULTILINE = true;

const DEFAULT_CHILDREN = false;
let originalConfig = context.options[0] || {};
if (SPACING_VALUES.includes(originalConfig))
originalConfig = Object.assign({ when: context.options[0] }, context.options[1]);
const defaultConfig = normalizeConfig(originalConfig, {
when: DEFAULT_WHEN,
allowMultiline: DEFAULT_ALLOW_MULTILINE,
allowMultiline: DEFAULT_ALLOW_MULTILINE
});
const attributes = has(originalConfig, 'attributes') ? originalConfig.attributes : DEFAULT_ATTRIBUTES;
const attributes = has(originalConfig, "attributes") ? originalConfig.attributes : DEFAULT_ATTRIBUTES;
const attributesConfig = attributes ? normalizeConfig(attributes, defaultConfig, true) : null;
const children = has(originalConfig, 'children') ? originalConfig.children : DEFAULT_CHILDREN;
const children = has(originalConfig, "children") ? originalConfig.children : DEFAULT_CHILDREN;
const childrenConfig = children ? normalizeConfig(children, defaultConfig, true) : null;
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
/**
* Determines whether two adjacent tokens have a newline between them.
* @param {object} left - The left token object.
* @param {object} right - The right token object.
* @returns {boolean} Whether or not there is a newline between the tokens.
*/
function isMultiline(left, right) {
return left.loc.end.line !== right.loc.start.line
return left.loc.end.line !== right.loc.start.line;
}
/**
* Trims text of whitespace between two ranges
* @param {Fixer} fixer - the eslint fixer object
* @param {number} fromLoc - the start location
* @param {number} toLoc - the end location
* @param {string} mode - either 'start' or 'end'
* @param {string=} spacing - a spacing value that will optionally add a space to the removed text
* @returns {object | * | {range, text}}
*/
function fixByTrimmingWhitespace(fixer, fromLoc, toLoc, mode, spacing) {
let replacementText = context.getSourceCode().text.slice(fromLoc, toLoc);
if (mode === 'start')
replacementText = replacementText.replace(/^\s+/gm, '');
if (mode === "start")
replacementText = replacementText.replace(/^\s+/gm, "");
else
replacementText = replacementText.replace(/\s+$/gm, '');
replacementText = replacementText.replace(/\s+$/gm, "");
if (spacing === SPACING.always) {
if (mode === 'start')
replacementText += ' ';
if (mode === "start")
replacementText += " ";
else
replacementText = ` ${replacementText}`;
}
return fixer.replaceTextRange([fromLoc, toLoc], replacementText)
return fixer.replaceTextRange([fromLoc, toLoc], replacementText);
}
/**
* Reports that there shouldn't be a newline after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {string} spacing
* @returns {void}
*/
function reportNoBeginningNewline(node, token, spacing) {
utils.report(context, messages.noNewlineAfter, 'noNewlineAfter', {
utils.report(context, messages.noNewlineAfter, "noNewlineAfter", {
node,
loc: token.loc.start,
data: {
token: token.value,
token: token.value
},
fix(fixer) {
const nextToken = context.getSourceCode().getTokenAfter(token);
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start', spacing)
},
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], "start", spacing);
}
});
}
/**
* Reports that there shouldn't be a newline before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @param {string} spacing
* @returns {void}
*/
function reportNoEndingNewline(node, token, spacing) {
utils.report(context, messages.noNewlineBefore, 'noNewlineBefore', {
utils.report(context, messages.noNewlineBefore, "noNewlineBefore", {
node,
loc: token.loc.start,
data: {
token: token.value,
token: token.value
},
fix(fixer) {
const previousToken = context.getSourceCode().getTokenBefore(token);
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end', spacing)
},
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], "end", spacing);
}
});
}
/**
* Reports that there shouldn't be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoBeginningSpace(node, token) {
utils.report(context, messages.noSpaceAfter, 'noSpaceAfter', {
utils.report(context, messages.noSpaceAfter, "noSpaceAfter", {
node,
loc: token.loc.start,
data: {
token: token.value,
token: token.value
},

@@ -256,34 +183,20 @@ fix(fixer) {

let nextComment;
// eslint >=4.x
if (sourceCode.getCommentsAfter) {
nextComment = sourceCode.getCommentsAfter(token);
// eslint 3.x
}
else {
} else {
const potentialComment = sourceCode.getTokenAfter(token, { includeComments: true });
nextComment = nextToken === potentialComment ? [] : [potentialComment];
}
// Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
if (nextComment.length > 0)
return fixByTrimmingWhitespace(fixer, token.range[1], Math.min(nextToken.range[0], nextComment[0].range[0]), 'start')
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], 'start')
},
return fixByTrimmingWhitespace(fixer, token.range[1], Math.min(nextToken.range[0], nextComment[0].range[0]), "start");
return fixByTrimmingWhitespace(fixer, token.range[1], nextToken.range[0], "start");
}
});
}
/**
* Reports that there shouldn't be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportNoEndingSpace(node, token) {
utils.report(context, messages.noSpaceBefore, 'noSpaceBefore', {
utils.report(context, messages.noSpaceBefore, "noSpaceBefore", {
node,
loc: token.loc.start,
data: {
token: token.value,
token: token.value
},

@@ -294,84 +207,54 @@ fix(fixer) {

let previousComment;
// eslint >=4.x
if (sourceCode.getCommentsBefore) {
previousComment = sourceCode.getCommentsBefore(token);
// eslint 3.x
}
else {
} else {
const potentialComment = sourceCode.getTokenBefore(token, { includeComments: true });
previousComment = previousToken === potentialComment ? [] : [potentialComment];
}
// Take comments into consideration to narrow the fix range to what is actually affected. (See #1414)
if (previousComment.length > 0)
return fixByTrimmingWhitespace(fixer, Math.max(previousToken.range[1], previousComment[0].range[1]), token.range[0], 'end')
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], 'end')
},
return fixByTrimmingWhitespace(fixer, Math.max(previousToken.range[1], previousComment[0].range[1]), token.range[0], "end");
return fixByTrimmingWhitespace(fixer, previousToken.range[1], token.range[0], "end");
}
});
}
/**
* Reports that there should be a space after the first token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredBeginningSpace(node, token) {
utils.report(context, messages.spaceNeededAfter, 'spaceNeededAfter', {
utils.report(context, messages.spaceNeededAfter, "spaceNeededAfter", {
node,
loc: token.loc.start,
data: {
token: token.value,
token: token.value
},
fix(fixer) {
return fixer.insertTextAfter(token, ' ')
},
return fixer.insertTextAfter(token, " ");
}
});
}
/**
* Reports that there should be a space before the last token
* @param {ASTNode} node - The node to report in the event of an error.
* @param {Token} token - The token to use for the report.
* @returns {void}
*/
function reportRequiredEndingSpace(node, token) {
utils.report(context, messages.spaceNeededBefore, 'spaceNeededBefore', {
utils.report(context, messages.spaceNeededBefore, "spaceNeededBefore", {
node,
loc: token.loc.start,
data: {
token: token.value,
token: token.value
},
fix(fixer) {
return fixer.insertTextBefore(token, ' ')
},
return fixer.insertTextBefore(token, " ");
}
});
}
/**
* Determines if spacing in curly braces is valid.
* @param {ASTNode} node The AST node to check.
* @returns {void}
*/
function validateBraceSpacing(node) {
let config;
switch (node.parent.type) {
case 'JSXAttribute':
case 'JSXOpeningElement':
case "JSXAttribute":
case "JSXOpeningElement":
config = attributesConfig;
break
case 'JSXElement':
case 'JSXFragment':
break;
case "JSXElement":
case "JSXFragment":
config = childrenConfig;
break
break;
default:
return
return;
}
if (config === null)
return
return;
const sourceCode = context.getSourceCode();

@@ -382,3 +265,2 @@ const first = sourceCode.getFirstToken(node);

let penultimate = sourceCode.getTokenBefore(last, { includeComments: true });
if (!second) {

@@ -394,3 +276,2 @@ second = sourceCode.getTokenAfter(first);

}
const isObjectLiteral = first.value === second.value;

@@ -403,3 +284,2 @@ const spacing = isObjectLiteral ? config.objectLiteralSpaces : config.when;

reportNoBeginningNewline(node, first, spacing);
if (!sourceCode.isSpaceBetweenTokens(penultimate, last))

@@ -409,9 +289,7 @@ reportRequiredEndingSpace(node, last);

reportNoEndingNewline(node, last, spacing);
}
else if (spacing === SPACING.never) {
} else if (spacing === SPACING.never) {
if (isMultiline(first, second)) {
if (!config.allowMultiline)
reportNoBeginningNewline(node, first, spacing);
}
else if (sourceCode.isSpaceBetweenTokens(first, second)) {
} else if (sourceCode.isSpaceBetweenTokens(first, second)) {
reportNoBeginningSpace(node, first);

@@ -422,4 +300,3 @@ }

reportNoEndingNewline(node, last, spacing);
}
else if (sourceCode.isSpaceBetweenTokens(penultimate, last)) {
} else if (sourceCode.isSpaceBetweenTokens(penultimate, last)) {
reportNoEndingSpace(node, last);

@@ -429,14 +306,9 @@ }

}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
JSXExpressionContainer: validateBraceSpacing,
JSXSpreadAttribute: validateBraceSpacing,
}
},
JSXSpreadAttribute: validateBraceSpacing
};
}
};
exports.jsxCurlySpacing = jsxCurlySpacing;

@@ -5,52 +5,26 @@ 'use strict';

/**
* @fileoverview Disallow or enforce spaces around equal signs in JSX attributes.
* @author ryym
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
noSpaceBefore: 'There should be no space before \'=\'',
noSpaceAfter: 'There should be no space after \'=\'',
needSpaceBefore: 'A space is required before \'=\'',
needSpaceAfter: 'A space is required after \'=\'',
noSpaceBefore: "There should be no space before '='",
noSpaceAfter: "There should be no space after '='",
needSpaceBefore: "A space is required before '='",
needSpaceAfter: "A space is required after '='"
};
var jsxEqualsSpacing = {
meta: {
docs: {
description: 'Enforce or disallow spaces around equal signs in JSX attributes',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-equals-spacing'),
description: "Enforce or disallow spaces around equal signs in JSX attributes",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-equals-spacing")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
enum: ['always', 'never'],
}],
enum: ["always", "never"]
}]
},
create(context) {
const config = context.options[0] || 'never';
/**
* Determines a given attribute node has an equal sign.
* @param {ASTNode} attrNode - The attribute node.
* @returns {boolean} Whether or not the attriute node has an equal sign.
*/
const config = context.options[0] || "never";
function hasEqual(attrNode) {
return attrNode.type !== 'JSXSpreadAttribute' && attrNode.value !== null
return attrNode.type !== "JSXSpreadAttribute" && attrNode.value !== null;
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {

@@ -60,4 +34,3 @@ JSXOpeningElement(node) {

if (!hasEqual(attrNode))
return
return;
const sourceCode = context.getSourceCode();

@@ -67,40 +40,38 @@ const equalToken = sourceCode.getTokenAfter(attrNode.name);

const spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value);
if (config === 'never') {
if (config === "never") {
if (spacedBefore) {
utils.report(context, messages.noSpaceBefore, 'noSpaceBefore', {
utils.report(context, messages.noSpaceBefore, "noSpaceBefore", {
node: attrNode,
loc: equalToken.loc.start,
fix(fixer) {
return fixer.removeRange([attrNode.name.range[1], equalToken.range[0]])
},
return fixer.removeRange([attrNode.name.range[1], equalToken.range[0]]);
}
});
}
if (spacedAfter) {
utils.report(context, messages.noSpaceAfter, 'noSpaceAfter', {
utils.report(context, messages.noSpaceAfter, "noSpaceAfter", {
node: attrNode,
loc: equalToken.loc.start,
fix(fixer) {
return fixer.removeRange([equalToken.range[1], attrNode.value.range[0]])
},
return fixer.removeRange([equalToken.range[1], attrNode.value.range[0]]);
}
});
}
}
else if (config === 'always') {
} else if (config === "always") {
if (!spacedBefore) {
utils.report(context, messages.needSpaceBefore, 'needSpaceBefore', {
utils.report(context, messages.needSpaceBefore, "needSpaceBefore", {
node: attrNode,
loc: equalToken.loc.start,
fix(fixer) {
return fixer.insertTextBefore(equalToken, ' ')
},
return fixer.insertTextBefore(equalToken, " ");
}
});
}
if (!spacedAfter) {
utils.report(context, messages.needSpaceAfter, 'needSpaceAfter', {
utils.report(context, messages.needSpaceAfter, "needSpaceAfter", {
node: attrNode,
loc: equalToken.loc.start,
fix(fixer) {
return fixer.insertTextAfter(equalToken, ' ')
},
return fixer.insertTextAfter(equalToken, " ");
}
});

@@ -110,7 +81,7 @@ }

});
},
}
},
}
};
}
};
exports.jsxEqualsSpacing = jsxEqualsSpacing;

@@ -5,80 +5,54 @@ 'use strict';

/**
* @fileoverview Ensure proper position of the first property in JSX
* @author Joachim Seminck
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
propOnNewLine: 'Property should be placed on a new line',
propOnSameLine: 'Property should be placed on the same line as the component declaration',
propOnNewLine: "Property should be placed on a new line",
propOnSameLine: "Property should be placed on the same line as the component declaration"
};
var jsxFirstPropNewLine = {
meta: {
docs: {
description: 'Enforce proper position of the first property in JSX',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-first-prop-new-line'),
description: "Enforce proper position of the first property in JSX",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-first-prop-new-line")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
enum: ['always', 'never', 'multiline', 'multiline-multiprop', 'multiprop'],
}],
enum: ["always", "never", "multiline", "multiline-multiprop", "multiprop"]
}]
},
create(context) {
const configuration = context.options[0] || 'multiline-multiprop';
const configuration = context.options[0] || "multiline-multiprop";
function isMultilineJSX(jsxNode) {
return jsxNode.loc.start.line < jsxNode.loc.end.line
return jsxNode.loc.start.line < jsxNode.loc.end.line;
}
return {
JSXOpeningElement(node) {
if (
(configuration === 'multiline' && isMultilineJSX(node))
|| (configuration === 'multiline-multiprop' && isMultilineJSX(node) && node.attributes.length > 1)
|| (configuration === 'multiprop' && node.attributes.length > 1)
|| (configuration === 'always')
) {
if (configuration === "multiline" && isMultilineJSX(node) || configuration === "multiline-multiprop" && isMultilineJSX(node) && node.attributes.length > 1 || configuration === "multiprop" && node.attributes.length > 1 || configuration === "always") {
node.attributes.some((decl) => {
if (decl.loc.start.line === node.loc.start.line) {
utils.report(context, messages.propOnNewLine, 'propOnNewLine', {
utils.report(context, messages.propOnNewLine, "propOnNewLine", {
node: decl,
fix(fixer) {
return fixer.replaceTextRange([(node.typeParameters || node.name).range[1], decl.range[0]], '\n')
},
return fixer.replaceTextRange([(node.typeParameters || node.name).range[1], decl.range[0]], "\n");
}
});
}
return true
return true;
});
}
else if (
(configuration === 'never' && node.attributes.length > 0)
|| (configuration === 'multiprop' && isMultilineJSX(node) && node.attributes.length <= 1)
) {
} else if (configuration === "never" && node.attributes.length > 0 || configuration === "multiprop" && isMultilineJSX(node) && node.attributes.length <= 1) {
const firstNode = node.attributes[0];
if (node.loc.start.line < firstNode.loc.start.line) {
utils.report(context, messages.propOnSameLine, 'propOnSameLine', {
utils.report(context, messages.propOnSameLine, "propOnSameLine", {
node: firstNode,
fix(fixer) {
return fixer.replaceTextRange([node.name.range[1], firstNode.range[0]], ' ')
},
return fixer.replaceTextRange([node.name.range[1], firstNode.range[0]], " ");
}
});
}
}
},
}
},
}
};
}
};
exports.jsxFirstPropNewLine = jsxFirstPropNewLine;

@@ -5,118 +5,61 @@ 'use strict';

/**
* @fileoverview Validate props indentation in JSX
* @author Yannick Croissant
*
* This rule has been ported and modified from eslint and nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
* @copyright 2015 Vitaly Puzrin. All rights reserved.
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
/*
Copyright (C) 2014 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
wrongIndent: "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."
};
var jsxIndentProps = {
meta: {
docs: {
description: 'Enforce props indentation in JSX',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-indent-props'),
description: "Enforce props indentation in JSX",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-indent-props")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
anyOf: [{
enum: ['tab', 'first'],
enum: ["tab", "first"]
}, {
type: 'integer',
type: "integer"
}, {
type: 'object',
type: "object",
properties: {
indentMode: {
anyOf: [{
enum: ['tab', 'first'],
enum: ["tab", "first"]
}, {
type: 'integer',
}],
type: "integer"
}]
},
ignoreTernaryOperator: {
type: 'boolean',
},
},
}],
}],
type: "boolean"
}
}
}]
}]
},
create(context) {
const extraColumnStart = 0;
let indentType = 'space';
/** @type {number|'first'} */
let indentType = "space";
let indentSize = 4;
const line = {
isUsingOperator: false,
currentOperator: false,
currentOperator: false
};
let ignoreTernaryOperator = false;
if (context.options.length) {
const isConfigObject = typeof context.options[0] === 'object';
const indentMode = isConfigObject
? context.options[0].indentMode
: context.options[0];
if (indentMode === 'first') {
indentSize = 'first';
indentType = 'space';
}
else if (indentMode === 'tab') {
const isConfigObject = typeof context.options[0] === "object";
const indentMode = isConfigObject ? context.options[0].indentMode : context.options[0];
if (indentMode === "first") {
indentSize = "first";
indentType = "space";
} else if (indentMode === "tab") {
indentSize = 1;
indentType = 'tab';
}
else if (typeof indentMode === 'number') {
indentType = "tab";
} else if (typeof indentMode === "number") {
indentSize = indentMode;
indentType = 'space';
indentType = "space";
}
if (isConfigObject && context.options[0].ignoreTernaryOperator)
ignoreTernaryOperator = true;
}
/**
* Reports a given indent violation and properly pluralizes the message
* @param {ASTNode} node Node violating the indent rule
* @param {number} needed Expected indentation character count
* @param {number} gotten Indentation character count in the actual node/code
*/
function report(node, needed, gotten) {

@@ -126,35 +69,25 @@ const msgContext = {

type: indentType,
characters: needed === 1 ? 'character' : 'characters',
gotten,
characters: needed === 1 ? "character" : "characters",
gotten
};
utils.report(context, messages.wrongIndent, 'wrongIndent', {
utils.report(context, messages.wrongIndent, "wrongIndent", {
node,
data: msgContext,
fix(fixer) {
return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], Array(needed + 1).join(indentType === 'space' ? ' ' : '\t'))
},
return fixer.replaceTextRange([node.range[0] - node.loc.start.column, node.range[0]], Array(needed + 1).join(indentType === "space" ? " " : " "));
}
});
}
/**
* Get node indent
* @param {ASTNode} node Node to examine
* @return {number} Indent
*/
function getNodeIndent(node) {
let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart);
const lines = src.split('\n');
const lines = src.split("\n");
src = lines[0];
let regExp;
if (indentType === 'space')
if (indentType === "space")
regExp = /^[ ]+/;
else
regExp = /^[\t]+/;
const indent = regExp.exec(src);
const useOperator = /^([ ]|[\t])*[:]/.test(src) || /^([ ]|[\t])*[?]/.test(src);
const useBracket = /[<]/.test(src);
line.currentOperator = false;

@@ -164,15 +97,7 @@ if (useOperator) {

line.currentOperator = true;
}
else if (useBracket) {
} else if (useBracket) {
line.isUsingOperator = false;
}
return indent ? indent[0].length : 0
return indent ? indent[0].length : 0;
}
/**
* Check indent for nodes list
* @param {ASTNode[]} nodes list of node objects
* @param {number} indent needed indent
*/
function checkNodesIndent(nodes, indent) {

@@ -182,30 +107,19 @@ let nestedIndent = indent;

const nodeIndent = getNodeIndent(node);
if (
line.isUsingOperator
&& !line.currentOperator
&& indentSize !== 'first'
&& !ignoreTernaryOperator
) {
if (line.isUsingOperator && !line.currentOperator && indentSize !== "first" && !ignoreTernaryOperator) {
nestedIndent += indentSize;
line.isUsingOperator = false;
}
if (
node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression'
&& nodeIndent !== nestedIndent && utils.isNodeFirstInLine(context, node)
)
if (node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && nodeIndent !== nestedIndent && utils.isNodeFirstInLine(context, node))
report(node, nestedIndent, nodeIndent);
});
}
return {
JSXOpeningElement(node) {
if (!node.attributes.length)
return
return;
let propIndent;
if (indentSize === 'first') {
if (indentSize === "first") {
const firstPropNode = node.attributes[0];
propIndent = firstPropNode.loc.start.column;
}
else {
} else {
const elementIndent = getNodeIndent(node);

@@ -215,7 +129,7 @@ propIndent = elementIndent + indentSize;

checkNodesIndent(node.attributes, propIndent);
},
}
},
}
};
}
};
exports.jsxIndentProps = jsxIndentProps;

@@ -5,146 +5,83 @@ 'use strict';

/**
* @fileoverview Validate JSX indentation
* @author Yannick Croissant
*
* This rule has been ported and modified from eslint and nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
* @copyright 2015 Vitaly Puzrin. All rights reserved.
* @copyright 2015 Gyandeep Singh. All rights reserved.
*/
/*
Copyright (C) 2014 by Vitaly Puzrin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
const matchAll = (s, v) => s.matchAll(v);
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
wrongIndent: "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."
};
var jsxIndent = {
meta: {
docs: {
description: 'Enforce JSX indentation',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-indent'),
description: "Enforce JSX indentation",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-indent")
},
fixable: 'whitespace',
fixable: "whitespace",
messages,
schema: [{
anyOf: [{
enum: ['tab'],
enum: ["tab"]
}, {
type: 'integer',
}],
type: "integer"
}]
}, {
type: 'object',
type: "object",
properties: {
checkAttributes: {
type: 'boolean',
type: "boolean"
},
indentLogicalExpressions: {
type: 'boolean',
},
type: "boolean"
}
},
additionalProperties: false,
}],
additionalProperties: false
}]
},
create(context) {
const extraColumnStart = 0;
let indentType = 'space';
let indentType = "space";
let indentSize = 4;
if (context.options.length) {
if (context.options[0] === 'tab') {
if (context.options[0] === "tab") {
indentSize = 1;
indentType = 'tab';
}
else if (typeof context.options[0] === 'number') {
indentType = "tab";
} else if (typeof context.options[0] === "number") {
indentSize = context.options[0];
indentType = 'space';
indentType = "space";
}
}
const indentChar = indentType === 'space' ? ' ' : '\t';
const indentChar = indentType === "space" ? " " : " ";
const options = context.options[1] || {};
const checkAttributes = options.checkAttributes || false;
const indentLogicalExpressions = options.indentLogicalExpressions || false;
/**
* Responsible for fixing the indentation issue fix
* @param {ASTNode} node Node violating the indent rule
* @param {number} needed Expected indentation character count
* @returns {Function} function to be executed by the fixer
* @private
*/
function getFixerFunction(node, needed) {
const indent = Array(needed + 1).join(indentChar);
if (node.type === 'JSXText' || node.type === 'Literal') {
if (node.type === "JSXText" || node.type === "Literal") {
return function fix(fixer) {
const regExp = /\n[\t ]*(\S)/g;
const fixedText = node.raw.replace(regExp, (match, p1) => `\n${indent}${p1}`);
return fixer.replaceText(node, fixedText)
}
const fixedText = node.raw.replace(regExp, (match, p1) => `
${indent}${p1}`);
return fixer.replaceText(node, fixedText);
};
}
if (node.type === 'ReturnStatement') {
if (node.type === "ReturnStatement") {
const raw = context.getSourceCode().getText(node);
const lines = raw.split('\n');
const lines = raw.split("\n");
if (lines.length > 1) {
return function fix(fixer) {
const lastLineStart = raw.lastIndexOf('\n');
const lastLine = raw.slice(lastLineStart).replace(/^\n[\t ]*(\S)/, (match, p1) => `\n${indent}${p1}`);
const lastLineStart = raw.lastIndexOf("\n");
const lastLine = raw.slice(lastLineStart).replace(/^\n[\t ]*(\S)/, (match, p1) => `
${indent}${p1}`);
return fixer.replaceTextRange(
[node.range[0] + lastLineStart, node.range[1]],
lastLine,
)
}
lastLine
);
};
}
}
return function fix(fixer) {
return fixer.replaceTextRange(
[node.range[0] - node.loc.start.column, node.range[0]],
indent,
)
}
indent
);
};
}
/**
* Reports a given indent violation and properly pluralizes the message
* @param {ASTNode} node Node violating the indent rule
* @param {number} needed Expected indentation character count
* @param {number} gotten Indentation character count in the actual node/code
* @param {object} [loc] Error line and column location
*/
function report(node, needed, gotten, loc) {

@@ -154,23 +91,14 @@ const msgContext = {

type: indentType,
characters: needed === 1 ? 'character' : 'characters',
gotten,
characters: needed === 1 ? "character" : "characters",
gotten
};
utils.report(context, messages.wrongIndent, 'wrongIndent', Object.assign({
utils.report(context, messages.wrongIndent, "wrongIndent", Object.assign({
node,
data: msgContext,
fix: getFixerFunction(node, needed),
fix: getFixerFunction(node, needed)
}, loc && { loc }));
}
/**
* Get node indent
* @param {ASTNode} node Node to examine
* @param {boolean} [byLastLine] get indent of node's last line
* @param {boolean} [excludeCommas] skip comma on start of line
* @return {number} Indent
*/
function getNodeIndent(node, byLastLine, excludeCommas) {
let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart);
const lines = src.split('\n');
const lines = src.split("\n");
if (byLastLine)

@@ -180,150 +108,45 @@ src = lines[lines.length - 1];

src = lines[0];
const skip = excludeCommas ? ',' : '';
const skip = excludeCommas ? "," : "";
let regExp;
if (indentType === 'space')
if (indentType === "space")
regExp = new RegExp(`^[ ${skip}]+`);
else
regExp = new RegExp(`^[\t${skip}]+`);
regExp = new RegExp(`^[ ${skip}]+`);
const indent = regExp.exec(src);
return indent ? indent[0].length : 0
return indent ? indent[0].length : 0;
}
/**
* Check if the node is the right member of a logical expression
* @param {ASTNode} node The node to check
* @return {boolean} true if its the case, false if not
*/
function isRightInLogicalExp(node) {
return (
node.parent
&& node.parent.parent
&& node.parent.parent.type === 'LogicalExpression'
&& node.parent.parent.right === node.parent
&& !indentLogicalExpressions
)
return node.parent && node.parent.parent && node.parent.parent.type === "LogicalExpression" && node.parent.parent.right === node.parent && !indentLogicalExpressions;
}
/**
* Check if the node is the alternate member of a conditional expression
* @param {ASTNode} node The node to check
* @return {boolean} true if its the case, false if not
*/
function isAlternateInConditionalExp(node) {
return (
node.parent
&& node.parent.parent
&& node.parent.parent.type === 'ConditionalExpression'
&& node.parent.parent.alternate === node.parent
&& context.getSourceCode().getTokenBefore(node).value !== '('
)
return node.parent && node.parent.parent && node.parent.parent.type === "ConditionalExpression" && node.parent.parent.alternate === node.parent && context.getSourceCode().getTokenBefore(node).value !== "(";
}
/**
* Check if the node is within a DoExpression block but not the first expression (which need to be indented)
* @param {ASTNode} node The node to check
* @return {boolean} true if its the case, false if not
*/
function isSecondOrSubsequentExpWithinDoExp(node) {
/*
It returns true when node.parent.parent.parent.parent matches:
DoExpression({
...,
body: BlockStatement({
...,
body: [
..., // 1-n times
ExpressionStatement({
...,
expression: JSXElement({
...,
openingElement: JSXOpeningElement() // the node
})
}),
... // 0-n times
]
})
})
except:
DoExpression({
...,
body: BlockStatement({
...,
body: [
ExpressionStatement({
...,
expression: JSXElement({
...,
openingElement: JSXOpeningElement() // the node
})
}),
... // 0-n times
]
})
})
*/
const isInExpStmt = (
node.parent
&& node.parent.parent
&& node.parent.parent.type === 'ExpressionStatement'
);
const isInExpStmt = node.parent && node.parent.parent && node.parent.parent.type === "ExpressionStatement";
if (!isInExpStmt)
return false
return false;
const expStmt = node.parent.parent;
const isInBlockStmtWithinDoExp = (
expStmt.parent
&& expStmt.parent.type === 'BlockStatement'
&& expStmt.parent.parent
&& expStmt.parent.parent.type === 'DoExpression'
);
const isInBlockStmtWithinDoExp = expStmt.parent && expStmt.parent.type === "BlockStatement" && expStmt.parent.parent && expStmt.parent.parent.type === "DoExpression";
if (!isInBlockStmtWithinDoExp)
return false
return false;
const blockStmt = expStmt.parent;
const blockStmtFirstExp = blockStmt.body[0];
return !(blockStmtFirstExp === expStmt)
return !(blockStmtFirstExp === expStmt);
}
/**
* Check indent for nodes list
* @param {ASTNode} node The node to check
* @param {number} indent needed indent
* @param {boolean} [excludeCommas] skip comma on start of line
*/
function checkNodesIndent(node, indent, excludeCommas) {
const nodeIndent = getNodeIndent(node, false, excludeCommas);
const isCorrectRightInLogicalExp = isRightInLogicalExp(node) && (nodeIndent - indent) === indentSize;
const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && (nodeIndent - indent) === 0;
if (
nodeIndent !== indent
&& utils.isNodeFirstInLine(context, node)
&& !isCorrectRightInLogicalExp
&& !isCorrectAlternateInCondExp
)
const isCorrectRightInLogicalExp = isRightInLogicalExp(node) && nodeIndent - indent === indentSize;
const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && nodeIndent - indent === 0;
if (nodeIndent !== indent && utils.isNodeFirstInLine(context, node) && !isCorrectRightInLogicalExp && !isCorrectAlternateInCondExp)
report(node, indent, nodeIndent);
}
/**
* Check indent for Literal Node or JSXText Node
* @param {ASTNode} node The node to check
* @param {number} indent needed indent
*/
function checkLiteralNodeIndent(node, indent) {
const value = node.value;
const regExp = indentType === 'space' ? /\n( *)[\t ]*\S/g : /\n(\t*)[\t ]*\S/g;
const regExp = indentType === "space" ? /\n( *)[\t ]*\S/g : /\n(\t*)[\t ]*\S/g;
const nodeIndentsPerLine = Array.from(
matchAll(String(value), regExp),
match => (match[1] ? match[1].length : 0),
(match) => match[1] ? match[1].length : 0
);
const hasFirstInLineNode = nodeIndentsPerLine.length > 0;
if (
hasFirstInLineNode
&& !nodeIndentsPerLine.every(actualIndent => actualIndent === indent)
) {
if (hasFirstInLineNode && !nodeIndentsPerLine.every((actualIndent) => actualIndent === indent)) {
nodeIndentsPerLine.forEach((nodeIndent) => {

@@ -334,3 +157,2 @@ report(node, indent, nodeIndent);

}
function handleOpeningElement(node) {

@@ -340,41 +162,28 @@ const sourceCode = context.getSourceCode();

if (!prevToken)
return
// Use the parent in a list or an array
if (prevToken.type === 'JSXText' || ((prevToken.type === 'Punctuator') && prevToken.value === ',')) {
return;
if (prevToken.type === "JSXText" || prevToken.type === "Punctuator" && prevToken.value === ",") {
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
prevToken = prevToken.type === 'Literal' || prevToken.type === 'JSXText' ? prevToken.parent : prevToken;
// Use the first non-punctuator token in a conditional expression
}
else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
prevToken = prevToken.type === "Literal" || prevToken.type === "JSXText" ? prevToken.parent : prevToken;
} else if (prevToken.type === "Punctuator" && prevToken.value === ":") {
do
prevToken = sourceCode.getTokenBefore(prevToken);
while (prevToken.type === 'Punctuator' && prevToken.value !== '/')
while (prevToken.type === "Punctuator" && prevToken.value !== "/");
prevToken = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression')
while (prevToken.parent && prevToken.parent.type !== "ConditionalExpression")
prevToken = prevToken.parent;
}
prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
prevToken = prevToken.type === "JSXExpressionContainer" ? prevToken.expression : prevToken;
const parentElementIndent = getNodeIndent(prevToken);
const indent = (
prevToken.loc.start.line === node.loc.start.line
|| isRightInLogicalExp(node)
|| isAlternateInConditionalExp(node)
|| isSecondOrSubsequentExpWithinDoExp(node)
) ? 0 : indentSize;
const indent = prevToken.loc.start.line === node.loc.start.line || isRightInLogicalExp(node) || isAlternateInConditionalExp(node) || isSecondOrSubsequentExpWithinDoExp(node) ? 0 : indentSize;
checkNodesIndent(node, parentElementIndent + indent);
}
function handleClosingElement(node) {
if (!node.parent)
return
return;
const peerElementIndent = getNodeIndent(node.parent.openingElement || node.parent.openingFragment);
checkNodesIndent(node, peerElementIndent);
}
function handleAttribute(node) {
if (!checkAttributes || (!node.value || node.value.type !== 'JSXExpressionContainer'))
return
if (!checkAttributes || (!node.value || node.value.type !== "JSXExpressionContainer"))
return;
const nameIndent = getNodeIndent(node.name);

@@ -386,14 +195,10 @@ const lastToken = context.getSourceCode().getLastToken(node.value);

}
function handleLiteral(node) {
if (!node.parent)
return
if (node.parent.type !== 'JSXElement' && node.parent.type !== 'JSXFragment')
return
return;
if (node.parent.type !== "JSXElement" && node.parent.type !== "JSXFragment")
return;
const parentNodeIndent = getNodeIndent(node.parent);
checkLiteralNodeIndent(node, parentNodeIndent + indentSize);
}
return {

@@ -407,4 +212,3 @@ JSXOpeningElement: handleOpeningElement,

if (!node.parent)
return
return;
const parentNodeIndent = getNodeIndent(node.parent);

@@ -415,30 +219,19 @@ checkNodesIndent(node, parentNodeIndent + indentSize);

JSXText: handleLiteral,
ReturnStatement(node) {
if (
!node.parent
|| !utils.isJSX(node.argument)
)
return
if (!node.parent || !utils.isJSX(node.argument))
return;
let fn = node.parent;
while (fn && fn.type !== 'FunctionDeclaration' && fn.type !== 'FunctionExpression')
while (fn && fn.type !== "FunctionDeclaration" && fn.type !== "FunctionExpression")
fn = fn.parent;
if (
!fn
|| !utils.isReturningJSX(node, context, true)
)
return
if (!fn || !utils.isReturningJSX(node, context, true))
return;
const openingIndent = getNodeIndent(node);
const closingIndent = getNodeIndent(node, true);
if (openingIndent !== closingIndent)
report(node, openingIndent, closingIndent);
},
}
},
}
};
}
};
exports.jsxIndent = jsxIndent;

@@ -5,85 +5,64 @@ 'use strict';

/**
* @fileoverview Limit maximum of props on a single line in JSX
* @author Yannick Croissant
*/
function getPropName(context, propNode) {
if (propNode.type === 'JSXSpreadAttribute')
return context.getSourceCode().getText(propNode.argument)
return propNode.name.name
if (propNode.type === "JSXSpreadAttribute")
return context.getSourceCode().getText(propNode.argument);
return propNode.name.name;
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
newLine: 'Prop `{{prop}}` must be placed on a new line',
newLine: "Prop `{{prop}}` must be placed on a new line"
};
var jsxMaxPropsPerLine = {
meta: {
docs: {
description: 'Enforce maximum of props on a single line in JSX',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-max-props-per-line'),
description: "Enforce maximum of props on a single line in JSX",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-max-props-per-line")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
anyOf: [{
type: 'object',
type: "object",
properties: {
maximum: {
type: 'object',
type: "object",
properties: {
single: {
type: 'integer',
minimum: 1,
type: "integer",
minimum: 1
},
multi: {
type: 'integer',
minimum: 1,
},
},
},
type: "integer",
minimum: 1
}
}
}
},
additionalProperties: false,
additionalProperties: false
}, {
type: 'object',
type: "object",
properties: {
maximum: {
type: 'number',
minimum: 1,
type: "number",
minimum: 1
},
when: {
type: 'string',
enum: ['always', 'multiline'],
},
type: "string",
enum: ["always", "multiline"]
}
},
additionalProperties: false,
}],
}],
additionalProperties: false
}]
}]
},
create(context) {
const configuration = context.options[0] || {};
const maximum = configuration.maximum || 1;
const maxConfig = typeof maximum === 'number'
? {
single: configuration.when === 'multiline' ? Infinity : maximum,
multi: maximum,
}
: {
single: maximum.single || Infinity,
multi: maximum.multi || Infinity,
};
const maxConfig = typeof maximum === "number" ? {
single: configuration.when === "multiline" ? Infinity : maximum,
multi: maximum
} : {
single: maximum.single || Infinity,
multi: maximum.multi || Infinity
};
function generateFixFunction(line, max) {

@@ -94,33 +73,24 @@ const sourceCode = context.getSourceCode();

const back = line[line.length - 1].range[1];
for (let i = 0; i < line.length; i += max) {
const nodes = line.slice(i, i + max);
output.push(nodes.reduce((prev, curr) => {
if (prev === '')
return sourceCode.getText(curr)
return `${prev} ${sourceCode.getText(curr)}`
}, ''));
if (prev === "")
return sourceCode.getText(curr);
return `${prev} ${sourceCode.getText(curr)}`;
}, ""));
}
const code = output.join('\n');
const code = output.join("\n");
return function fix(fixer) {
return fixer.replaceTextRange([front, back], code)
}
return fixer.replaceTextRange([front, back], code);
};
}
return {
JSXOpeningElement(node) {
if (!node.attributes.length)
return
return;
const isSingleLineTag = node.loc.start.line === node.loc.end.line;
if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity)
return
return;
const firstProp = node.attributes[0];
const linePartitionedProps = [[firstProp]];
node.attributes.reduce((last, decl) => {

@@ -131,27 +101,22 @@ if (last.loc.end.line === decl.loc.start.line)

linePartitionedProps.push([decl]);
return decl
return decl;
});
linePartitionedProps.forEach((propsInLine) => {
const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line
? maxConfig.single
: maxConfig.multi;
const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line ? maxConfig.single : maxConfig.multi;
if (propsInLine.length > maxPropsCountPerLine) {
const name = getPropName(context, propsInLine[maxPropsCountPerLine]);
utils.report(context, messages.newLine, 'newLine', {
utils.report(context, messages.newLine, "newLine", {
node: propsInLine[maxPropsCountPerLine],
data: {
prop: name,
prop: name
},
fix: generateFixFunction(propsInLine, maxPropsCountPerLine),
fix: generateFixFunction(propsInLine, maxPropsCountPerLine)
});
}
});
},
}
},
}
};
}
};
exports.jsxMaxPropsPerLine = jsxMaxPropsPerLine;

@@ -5,46 +5,31 @@ 'use strict';

/**
* @fileoverview Require or prevent a new line after jsx elements and expressions.
* @author Johnny Zabala
* @author Joseph Stiles
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
require: 'JSX element should start in a new line',
prevent: 'JSX element should not start in a new line',
allowMultilines: 'Multiline JSX elements should start in a new line',
require: "JSX element should start in a new line",
prevent: "JSX element should not start in a new line",
allowMultilines: "Multiline JSX elements should start in a new line"
};
function isMultilined(node) {
return node && node.loc.start.line !== node.loc.end.line
return node && node.loc.start.line !== node.loc.end.line;
}
var jsxNewline = {
meta: {
docs: {
description: 'Require or prevent a new line after jsx elements and expressions.',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-newline'),
description: "Require or prevent a new line after jsx elements and expressions.",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-newline")
},
fixable: 'code',
fixable: "code",
messages,
schema: [
{
type: 'object',
type: "object",
properties: {
prevent: {
default: false,
type: 'boolean',
type: "boolean"
},
allowMultilines: {
default: false,
type: 'boolean',
},
type: "boolean"
}
},

@@ -55,5 +40,5 @@ additionalProperties: false,

allowMultilines: {
const: true,
},
},
const: true
}
}
},

@@ -63,64 +48,45 @@ then: {

prevent: {
const: true,
},
const: true
}
},
required: [
'prevent',
],
},
},
],
"prevent"
]
}
}
]
},
create(context) {
const jsxElementParents = new Set();
const jsxElementParents = /* @__PURE__ */ new Set();
const sourceCode = context.getSourceCode();
function isBlockCommentInCurlyBraces(element) {
const elementRawValue = sourceCode.getText(element);
return /^\s*{\/\*/.test(elementRawValue)
return /^\s*{\/\*/.test(elementRawValue);
}
function isNonBlockComment(element) {
return !isBlockCommentInCurlyBraces(element) && (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer')
return !isBlockCommentInCurlyBraces(element) && (element.type === "JSXElement" || element.type === "JSXExpressionContainer");
}
return {
'Program:exit': function () {
"Program:exit": function() {
jsxElementParents.forEach((parent) => {
parent.children.forEach((element, index, elements) => {
if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
if (element.type === "JSXElement" || element.type === "JSXExpressionContainer") {
const configuration = context.options[0] || {};
const prevent = configuration.prevent || false;
const allowMultilines = configuration.allowMultilines || false;
const firstAdjacentSibling = elements[index + 1];
const secondAdjacentSibling = elements[index + 2];
const hasSibling = firstAdjacentSibling
&& secondAdjacentSibling
&& (firstAdjacentSibling.type === 'Literal' || firstAdjacentSibling.type === 'JSXText');
const hasSibling = firstAdjacentSibling && secondAdjacentSibling && (firstAdjacentSibling.type === "Literal" || firstAdjacentSibling.type === "JSXText");
if (!hasSibling)
return
// Check adjacent sibling has the proper amount of newlines
return;
const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);
if (isBlockCommentInCurlyBraces(element))
return
if (
allowMultilines
&& (
isMultilined(element)
|| isMultilined(elements.slice(index + 2).find(isNonBlockComment))
)
) {
return;
if (allowMultilines && (isMultilined(element) || isMultilined(elements.slice(index + 2).find(isNonBlockComment)))) {
if (!isWithoutNewLine)
return
const regex = /(\n)(?!.*\1)/g;
const replacement = '\n\n';
const messageId = 'allowMultilines';
utils.report(context, messages[messageId], messageId, {
return;
const regex2 = /(\n)(?!.*\1)/g;
const replacement2 = "\n\n";
const messageId2 = "allowMultilines";
utils.report(context, messages[messageId2], messageId2, {
node: secondAdjacentSibling,

@@ -130,26 +96,13 @@ fix(fixer) {

firstAdjacentSibling,
sourceCode.getText(firstAdjacentSibling)
.replace(regex, replacement),
)
},
sourceCode.getText(firstAdjacentSibling).replace(regex2, replacement2)
);
}
});
return
return;
}
if (isWithoutNewLine === prevent)
return
const messageId = prevent
? 'prevent'
: 'require';
const regex = prevent
? /(\n\n)(?!.*\1)/g
: /(\n)(?!.*\1)/g;
const replacement = prevent
? '\n'
: '\n\n';
return;
const messageId = prevent ? "prevent" : "require";
const regex = prevent ? /(\n\n)(?!.*\1)/g : /(\n)(?!.*\1)/g;
const replacement = prevent ? "\n" : "\n\n";
utils.report(context, messages[messageId], messageId, {

@@ -161,6 +114,5 @@ node: secondAdjacentSibling,

// double or remove the last newline
sourceCode.getText(firstAdjacentSibling)
.replace(regex, replacement),
)
},
sourceCode.getText(firstAdjacentSibling).replace(regex, replacement)
);
}
});

@@ -171,9 +123,9 @@ }

},
':matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)': (node) => {
":matches(JSXElement, JSXFragment) > :matches(JSXElement, JSXExpressionContainer)": (node) => {
jsxElementParents.add(node.parent);
},
}
},
}
};
}
};
exports.jsxNewline = jsxNewline;

@@ -5,63 +5,42 @@ 'use strict';

/**
* @fileoverview Limit to one expression per line in JSX
* @author Mark Ivan Allen <Vydia.com>
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const optionDefaults = {
allow: 'none',
allow: "none"
};
const messages = {
moveToNewLine: '`{{descriptor}}` must be placed on a new line',
moveToNewLine: "`{{descriptor}}` must be placed on a new line"
};
var jsxOneExpressionPerLine = {
meta: {
docs: {
description: 'Require one JSX element per line',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-one-expression-per-line'),
description: "Require one JSX element per line",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-one-expression-per-line")
},
fixable: 'whitespace',
fixable: "whitespace",
messages,
schema: [
{
type: 'object',
type: "object",
properties: {
allow: {
enum: ['none', 'literal', 'single-child'],
},
enum: ["none", "literal", "single-child"]
}
},
default: optionDefaults,
additionalProperties: false,
},
],
additionalProperties: false
}
]
},
create(context) {
const options = Object.assign({}, optionDefaults, context.options[0]);
function nodeKey(node) {
return `${node.loc.start.line},${node.loc.start.column}`
return `${node.loc.start.line},${node.loc.start.column}`;
}
function nodeDescriptor(n) {
return n.openingElement ? n.openingElement.name.name : context.getSourceCode().getText(n).replace(/\n/g, '')
return n.openingElement ? n.openingElement.name.name : context.getSourceCode().getText(n).replace(/\n/g, "");
}
function handleJSX(node) {
const children = node.children;
if (!children || !children.length)
return
return;
const openingElement = node.openingElement || node.openingFragment;

@@ -73,56 +52,35 @@ const closingElement = node.closingElement || node.closingFragment;

const closingElementEndLine = closingElement.loc.end.line;
if (children.length === 1) {
const child = children[0];
if (
openingElementStartLine === openingElementEndLine
&& openingElementEndLine === closingElementStartLine
&& closingElementStartLine === closingElementEndLine
&& closingElementEndLine === child.loc.start.line
&& child.loc.start.line === child.loc.end.line
) {
if (
options.allow === 'single-child'
|| (options.allow === 'literal' && (child.type === 'Literal' || child.type === 'JSXText'))
)
return
if (openingElementStartLine === openingElementEndLine && openingElementEndLine === closingElementStartLine && closingElementStartLine === closingElementEndLine && closingElementEndLine === child.loc.start.line && child.loc.start.line === child.loc.end.line) {
if (options.allow === "single-child" || options.allow === "literal" && (child.type === "Literal" || child.type === "JSXText"))
return;
}
}
const childrenGroupedByLine = {};
const fixDetailsByNode = {};
children.forEach((child) => {
let countNewLinesBeforeContent = 0;
let countNewLinesAfterContent = 0;
if (child.type === 'Literal' || child.type === 'JSXText') {
if (child.type === "Literal" || child.type === "JSXText") {
if (utils.isWhiteSpaces(child.raw))
return
return;
countNewLinesBeforeContent = (child.raw.match(/^\s*\n/g) || []).length;
countNewLinesAfterContent = (child.raw.match(/\n\s*$/g) || []).length;
}
const startLine = child.loc.start.line + countNewLinesBeforeContent;
const endLine = child.loc.end.line - countNewLinesAfterContent;
if (startLine === endLine) {
if (!childrenGroupedByLine[startLine])
childrenGroupedByLine[startLine] = [];
childrenGroupedByLine[startLine].push(child);
}
else {
} else {
if (!childrenGroupedByLine[startLine])
childrenGroupedByLine[startLine] = [];
childrenGroupedByLine[startLine].push(child);
if (!childrenGroupedByLine[endLine])
childrenGroupedByLine[endLine] = [];
childrenGroupedByLine[endLine].push(child);
}
});
Object.keys(childrenGroupedByLine).forEach((_line) => {

@@ -132,15 +90,11 @@ const line = parseInt(_line, 10);

const lastIndex = childrenGroupedByLine[line].length - 1;
childrenGroupedByLine[line].forEach((child, i) => {
let prevChild;
let nextChild;
if (i === firstIndex) {
if (line === openingElementEndLine)
prevChild = openingElement;
}
else {
} else {
prevChild = childrenGroupedByLine[line][i - 1];
}
if (i === lastIndex) {

@@ -150,18 +104,10 @@ if (line === closingElementStartLine)

}
function spaceBetweenPrev() {
return ((prevChild.type === 'Literal' || prevChild.type === 'JSXText') && prevChild.raw.endsWith(' '))
|| ((child.type === 'Literal' || child.type === 'JSXText') && child.raw.startsWith(' '))
|| context.getSourceCode().isSpaceBetweenTokens(prevChild, child)
return (prevChild.type === "Literal" || prevChild.type === "JSXText") && prevChild.raw.endsWith(" ") || (child.type === "Literal" || child.type === "JSXText") && child.raw.startsWith(" ") || context.getSourceCode().isSpaceBetweenTokens(prevChild, child);
}
function spaceBetweenNext() {
return ((nextChild.type === 'Literal' || nextChild.type === 'JSXText') && nextChild.raw.startsWith(' '))
|| ((child.type === 'Literal' || child.type === 'JSXText') && child.raw.endsWith(' '))
|| context.getSourceCode().isSpaceBetweenTokens(child, nextChild)
return (nextChild.type === "Literal" || nextChild.type === "JSXText") && nextChild.raw.startsWith(" ") || (child.type === "Literal" || child.type === "JSXText") && child.raw.endsWith(" ") || context.getSourceCode().isSpaceBetweenTokens(child, nextChild);
}
if (!prevChild && !nextChild)
return
return;
const source = context.getSourceCode().getText(child);

@@ -172,5 +118,3 @@ const leadingSpace = !!(prevChild && spaceBetweenPrev());

const trailingNewLine = !!nextChild;
const key = nodeKey(child);
if (!fixDetailsByNode[key]) {

@@ -180,15 +124,11 @@ fixDetailsByNode[key] = {

source,
descriptor: nodeDescriptor(child),
descriptor: nodeDescriptor(child)
};
}
if (leadingSpace)
fixDetailsByNode[key].leadingSpace = true;
if (leadingNewLine)
fixDetailsByNode[key].leadingNewLine = true;
if (trailingNewLine)
fixDetailsByNode[key].trailingNewLine = true;
if (trailingSpace)

@@ -198,36 +138,30 @@ fixDetailsByNode[key].trailingSpace = true;

});
Object.keys(fixDetailsByNode).forEach((key) => {
const details = fixDetailsByNode[key];
const nodeToReport = details.node;
const descriptor = details.descriptor;
const source = details.source.replace(/(^ +| +(?=\n)*$)/g, '');
const leadingSpaceString = details.leadingSpace ? '\n{\' \'}' : '';
const trailingSpaceString = details.trailingSpace ? '{\' \'}\n' : '';
const leadingNewLineString = details.leadingNewLine ? '\n' : '';
const trailingNewLineString = details.trailingNewLine ? '\n' : '';
const source = details.source.replace(/(^ +| +(?=\n)*$)/g, "");
const leadingSpaceString = details.leadingSpace ? "\n{' '}" : "";
const trailingSpaceString = details.trailingSpace ? "{' '}\n" : "";
const leadingNewLineString = details.leadingNewLine ? "\n" : "";
const trailingNewLineString = details.trailingNewLine ? "\n" : "";
const replaceText = `${leadingSpaceString}${leadingNewLineString}${source}${trailingNewLineString}${trailingSpaceString}`;
utils.report(context, messages.moveToNewLine, 'moveToNewLine', {
utils.report(context, messages.moveToNewLine, "moveToNewLine", {
node: nodeToReport,
data: {
descriptor,
descriptor
},
fix(fixer) {
return fixer.replaceText(nodeToReport, replaceText)
},
return fixer.replaceText(nodeToReport, replaceText);
}
});
});
}
return {
JSXElement: handleJSX,
JSXFragment: handleJSX,
}
},
JSXFragment: handleJSX
};
}
};
exports.jsxOneExpressionPerLine = jsxOneExpressionPerLine;

@@ -5,55 +5,34 @@ 'use strict';

/**
* @fileoverview Disallow multiple spaces between inline JSX props
* @author Adrian Moennich
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
noLineGap: 'Expected no line gap between “{{prop1}}” and “{{prop2}}”',
onlyOneSpace: 'Expected only one space between “{{prop1}}” and “{{prop2}}”',
noLineGap: "Expected no line gap between \u201C{{prop1}}\u201D and \u201C{{prop2}}\u201D",
onlyOneSpace: "Expected only one space between \u201C{{prop1}}\u201D and \u201C{{prop2}}\u201D"
};
var jsxPropsNoMultiSpaces = {
meta: {
docs: {
description: 'Disallow multiple spaces between inline JSX props',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-props-no-multi-spaces'),
description: "Disallow multiple spaces between inline JSX props",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-props-no-multi-spaces")
},
fixable: 'code',
fixable: "code",
messages,
schema: [],
schema: []
},
create(context) {
const sourceCode = context.getSourceCode();
function getPropName(propNode) {
switch (propNode.type) {
case 'JSXSpreadAttribute':
return context.getSourceCode().getText(propNode.argument)
case 'JSXIdentifier':
return propNode.name
case 'JSXMemberExpression':
return `${getPropName(propNode.object)}.${propNode.property.name}`
case "JSXSpreadAttribute":
return context.getSourceCode().getText(propNode.argument);
case "JSXIdentifier":
return propNode.name;
case "JSXMemberExpression":
return `${getPropName(propNode.object)}.${propNode.property.name}`;
default:
return propNode.name
? propNode.name.name
: `${context.getSourceCode().getText(propNode.object)}.${propNode.property.name}` // needed for typescript-eslint parser
return propNode.name ? propNode.name.name : `${context.getSourceCode().getText(propNode.object)}.${propNode.property.name}`;
}
}
// First and second must be adjacent nodes
function hasEmptyLines(first, second) {
const comments = sourceCode.getCommentsBefore ? sourceCode.getCommentsBefore(second) : [];
const nodes = [].concat(first, comments, second);
for (let i = 1; i < nodes.length; i += 1) {

@@ -63,43 +42,36 @@ const prev = nodes[i - 1];

if (curr.loc.start.line - prev.loc.end.line >= 2)
return true
return true;
}
return false
return false;
}
function checkSpacing(prev, node) {
if (hasEmptyLines(prev, node)) {
utils.report(context, messages.noLineGap, 'noLineGap', {
utils.report(context, messages.noLineGap, "noLineGap", {
node,
data: {
prop1: getPropName(prev),
prop2: getPropName(node),
},
prop2: getPropName(node)
}
});
}
if (prev.loc.end.line !== node.loc.end.line)
return
return;
const between = context.getSourceCode().text.slice(prev.range[1], node.range[0]);
if (between !== ' ') {
utils.report(context, messages.onlyOneSpace, 'onlyOneSpace', {
if (between !== " ") {
utils.report(context, messages.onlyOneSpace, "onlyOneSpace", {
node,
data: {
prop1: getPropName(prev),
prop2: getPropName(node),
prop2: getPropName(node)
},
fix(fixer) {
return fixer.replaceTextRange([prev.range[1], node.range[0]], ' ')
},
return fixer.replaceTextRange([prev.range[1], node.range[0]], " ");
}
});
}
}
function containsGenericType(node) {
const containsTypeParams = typeof node.typeParameters !== 'undefined';
return containsTypeParams && node.typeParameters.type === 'TSTypeParameterInstantiation'
const containsTypeParams = typeof node.typeParameters !== "undefined";
return containsTypeParams && node.typeParameters.type === "TSTypeParameterInstantiation";
}
function getGenericNode(node) {

@@ -109,3 +81,2 @@ const name = node.name;

const type = node.typeParameters;
return Object.assign(

@@ -117,11 +88,9 @@ {},

name.range[0],
type.range[1],
],
},
)
type.range[1]
]
}
);
}
return name
return name;
}
return {

@@ -131,9 +100,9 @@ JSXOpeningElement(node) {

checkSpacing(prev, prop);
return prop
return prop;
}, getGenericNode(node));
},
}
},
}
};
}
};
exports.jsxPropsNoMultiSpaces = jsxPropsNoMultiSpaces;

@@ -5,101 +5,63 @@ 'use strict';

/**
* @fileoverview Prevent extra closing tags for components without children
* @author Yannick Croissant
*/
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const optionDefaults = { component: true, html: true };
const messages = {
notSelfClosing: 'Empty components are self-closing',
notSelfClosing: "Empty components are self-closing"
};
var jsxSelfClosingComp = {
meta: {
docs: {
description: 'Disallow extra closing tags for components without children',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('self-closing-comp'),
description: "Disallow extra closing tags for components without children",
category: "Stylistic Issues",
url: utils.docsUrl("self-closing-comp")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
type: 'object',
type: "object",
properties: {
component: {
default: optionDefaults.component,
type: 'boolean',
type: "boolean"
},
html: {
default: optionDefaults.html,
type: 'boolean',
},
type: "boolean"
}
},
additionalProperties: false,
}],
additionalProperties: false
}]
},
create(context) {
function isComponent(node) {
return (
node.name
&& (node.name.type === 'JSXIdentifier' || node.name.type === 'JSXMemberExpression')
&& !utils.isDOMComponent(node)
)
return node.name && (node.name.type === "JSXIdentifier" || node.name.type === "JSXMemberExpression") && !utils.isDOMComponent(node);
}
function childrenIsEmpty(node) {
return node.parent.children.length === 0
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.includes('\n')
&& childrens[0].value.replace(/(?!\xA0)\s/g, '') === ''
)
return childrens.length === 1 && (childrens[0].type === "Literal" || childrens[0].type === "JSXText") && childrens[0].value.includes("\n") && childrens[0].value.replace(/(?!\xA0)\s/g, "") === "";
}
function isShouldBeSelfClosed(node) {
const configuration = Object.assign({}, optionDefaults, context.options[0]);
return (
(configuration.component && isComponent(node))
|| (configuration.html && utils.isDOMComponent(node))
) && !node.selfClosing && (childrenIsEmpty(node) || childrenIsMultilineSpaces(node))
return (configuration.component && isComponent(node) || configuration.html && utils.isDOMComponent(node)) && !node.selfClosing && (childrenIsEmpty(node) || childrenIsMultilineSpaces(node));
}
return {
JSXOpeningElement(node) {
if (!isShouldBeSelfClosed(node))
return
utils.report(context, messages.notSelfClosing, 'notSelfClosing', {
return;
utils.report(context, messages.notSelfClosing, "notSelfClosing", {
node,
fix(fixer) {
// Represents the last character of the JSXOpeningElement, the '>' character
const openingElementEnding = node.range[1] - 1;
// Represents the last character of the JSXClosingElement, the '>' character
const closingElementEnding = node.parent.closingElement.range[1];
// Replace />.*<\/.*>/ with '/>'
const range = [openingElementEnding, closingElementEnding];
return fixer.replaceTextRange(range, ' />')
},
return fixer.replaceTextRange(range, " />");
}
});
},
}
},
}
};
}
};
exports.jsxSelfClosingComp = jsxSelfClosingComp;

@@ -5,66 +5,44 @@ 'use strict';

/**
* @fileoverview Enforce props alphabetical sorting
* @author Ilya Volodin, Yannick Croissant
*/
const includes = (arr, value) => arr.includes(value);
const toSorted = (arr, compareFn) => [...arr].sort(compareFn);
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function isCallbackPropName(name) {
return /^on[A-Z]/.test(name)
return /^on[A-Z]/.test(name);
}
function isMultilineProp(node) {
return node.loc.start.line !== node.loc.end.line
return node.loc.start.line !== node.loc.end.line;
}
const messages = {
noUnreservedProps: 'A customized reserved first list must only contain a subset of React reserved props. Remove: {{unreservedWords}}',
listIsEmpty: 'A customized reserved first list must not be empty',
listReservedPropsFirst: 'Reserved props must be listed before all other props',
listCallbacksLast: 'Callbacks must be listed after all other props',
listShorthandFirst: 'Shorthand props must be listed before all other props',
listShorthandLast: 'Shorthand props must be listed after all other props',
listMultilineFirst: 'Multiline props must be listed before all other props',
listMultilineLast: 'Multiline props must be listed after all other props',
sortPropsByAlpha: 'Props should be sorted alphabetically',
noUnreservedProps: "A customized reserved first list must only contain a subset of React reserved props. Remove: {{unreservedWords}}",
listIsEmpty: "A customized reserved first list must not be empty",
listReservedPropsFirst: "Reserved props must be listed before all other props",
listCallbacksLast: "Callbacks must be listed after all other props",
listShorthandFirst: "Shorthand props must be listed before all other props",
listShorthandLast: "Shorthand props must be listed after all other props",
listMultilineFirst: "Multiline props must be listed before all other props",
listMultilineLast: "Multiline props must be listed after all other props",
sortPropsByAlpha: "Props should be sorted alphabetically"
};
const RESERVED_PROPS_LIST = [
'children',
'dangerouslySetInnerHTML',
'key',
'ref',
"children",
"dangerouslySetInnerHTML",
"key",
"ref"
];
function isReservedPropName(name, list) {
return list.includes(name)
return list.includes(name);
}
let attributeMap;
// attributeMap = { end: endrange, hasComment: true||false if comment in between nodes exists, it needs to be sorted to end }
function shouldSortToEnd(node) {
const attr = attributeMap.get(node);
return !!attr && !!attr.hasComment
return !!attr && !!attr.hasComment;
}
function contextCompare(a, b, options) {
let aProp = utils.getPropName(a);
let bProp = utils.getPropName(b);
const aSortToEnd = shouldSortToEnd(a);
const bSortToEnd = shouldSortToEnd(b);
if (aSortToEnd && !bSortToEnd)
return 1
return 1;
if (!aSortToEnd && bSortToEnd)
return -1
return -1;
if (options.reservedFirst) {

@@ -74,8 +52,6 @@ const aIsReserved = isReservedPropName(aProp, options.reservedList);

if (aIsReserved && !bIsReserved)
return -1
return -1;
if (!aIsReserved && bIsReserved)
return 1
return 1;
}
if (options.callbacksLast) {

@@ -85,57 +61,38 @@ const aIsCallback = isCallbackPropName(aProp);

if (aIsCallback && !bIsCallback)
return 1
return 1;
if (!aIsCallback && bIsCallback)
return -1
return -1;
}
if (options.shorthandFirst || options.shorthandLast) {
const shorthandSign = options.shorthandFirst ? -1 : 1;
if (!a.value && b.value)
return shorthandSign
return shorthandSign;
if (a.value && !b.value)
return -shorthandSign
return -shorthandSign;
}
if (options.multiline !== 'ignore') {
const multilineSign = options.multiline === 'first' ? -1 : 1;
if (options.multiline !== "ignore") {
const multilineSign = options.multiline === "first" ? -1 : 1;
const aIsMultiline = isMultilineProp(a);
const bIsMultiline = isMultilineProp(b);
if (aIsMultiline && !bIsMultiline)
return multilineSign
return multilineSign;
if (!aIsMultiline && bIsMultiline)
return -multilineSign
return -multilineSign;
}
if (options.noSortAlphabetically)
return 0
const actualLocale = options.locale === 'auto' ? undefined : options.locale;
return 0;
const actualLocale = options.locale === "auto" ? void 0 : options.locale;
if (options.ignoreCase) {
aProp = aProp.toLowerCase();
bProp = bProp.toLowerCase();
return aProp.localeCompare(bProp, actualLocale)
return aProp.localeCompare(bProp, actualLocale);
}
if (aProp === bProp)
return 0
if (options.locale === 'auto')
return aProp < bProp ? -1 : 1
return aProp.localeCompare(bProp, actualLocale)
return 0;
if (options.locale === "auto")
return aProp < bProp ? -1 : 1;
return aProp.localeCompare(bProp, actualLocale);
}
/**
* Create an array of arrays where each subarray is composed of attributes
* that are considered sortable.
* @param {Array<JSXSpreadAttribute|JSXAttribute>} attributes
* @param {object} context The context of the rule
* @return {Array<Array<JSXAttribute>>}
*/
function getGroupsOfSortableAttributes(attributes, context) {
const sourceCode = context.getSourceCode();
const sortableAttributeGroups = [];

@@ -146,3 +103,2 @@ let groupCount = 0;

}
for (let i = 0; i < attributes.length; i++) {

@@ -155,14 +111,7 @@ const attribute = attributes[i];

comment = sourceCode.getCommentsAfter(attribute);
} catch (e) {
}
catch (e) { /**/ }
const lastAttr = attributes[i - 1];
const attrIsSpread = attribute.type === 'JSXSpreadAttribute';
// If we have no groups or if the last attribute was JSXSpreadAttribute
// then we start a new group. Append attributes to the group until we
// come across another JSXSpreadAttribute or exhaust the array.
if (
!lastAttr
|| (lastAttr.type === 'JSXSpreadAttribute' && !attrIsSpread)
) {
const attrIsSpread = attribute.type === "JSXSpreadAttribute";
if (!lastAttr || lastAttr.type === "JSXSpreadAttribute" && !attrIsSpread) {
groupCount += 1;

@@ -175,4 +124,3 @@ sortableAttributeGroups[groupCount - 1] = [];

addtoSortableAttributeGroups(attribute);
}
else {
} else {
const firstComment = comment[0];

@@ -185,12 +133,9 @@ const commentline = firstComment.loc.start.line;

i += 1;
}
else if (attributeline === commentline) {
if (firstComment.type === 'Block' && nextAttribute) {
} else if (attributeline === commentline) {
if (firstComment.type === "Block" && nextAttribute) {
attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true });
i += 1;
}
else if (firstComment.type === 'Block') {
} else if (firstComment.type === "Block") {
attributeMap.set(attribute, { end: firstComment.range[1], hasComment: true });
}
else {
} else {
attributeMap.set(attribute, { end: firstComment.range[1], hasComment: false });

@@ -200,12 +145,7 @@ }

}
}
else if (comment.length > 1 && attributeline + 1 === comment[1].loc.start.line && nextAttribute) {
} else if (comment.length > 1 && attributeline + 1 === comment[1].loc.start.line && nextAttribute) {
const commentNextAttribute = sourceCode.getCommentsAfter(nextAttribute);
attributeMap.set(attribute, { end: nextAttribute.range[1], hasComment: true });
if (
commentNextAttribute.length === 1
&& nextAttribute.loc.start.line === commentNextAttribute[0].loc.start.line
)
if (commentNextAttribute.length === 1 && nextAttribute.loc.start.line === commentNextAttribute[0].loc.start.line)
attributeMap.set(attribute, { end: commentNextAttribute[0].range[1], hasComment: true });
addtoSortableAttributeGroups(attribute);

@@ -217,5 +157,4 @@ i += 1;

}
return sortableAttributeGroups
return sortableAttributeGroups;
}
function generateFixerFunction(node, context, reservedList) {

@@ -229,10 +168,6 @@ const sourceCode = context.getSourceCode();

const shorthandLast = configuration.shorthandLast || false;
const multiline = configuration.multiline || 'ignore';
const multiline = configuration.multiline || "ignore";
const noSortAlphabetically = configuration.noSortAlphabetically || false;
const reservedFirst = configuration.reservedFirst || false;
const locale = configuration.locale || 'auto';
// Sort props according to the context. Only supports ignoreCase.
// Since we cannot safely move JSXSpreadAttribute (due to potential variable overrides),
// we only consider groups of sortable attributes.
const locale = configuration.locale || "auto";
const options = {

@@ -247,13 +182,9 @@ ignoreCase,

reservedList,
locale,
locale
};
const sortableAttributeGroups = getGroupsOfSortableAttributes(attributes, context);
const sortedAttributeGroups = sortableAttributeGroups
.slice(0)
.map(group => toSorted(group, (a, b) => contextCompare(a, b, options)));
const sortedAttributeGroups = sortableAttributeGroups.slice(0).map((group) => toSorted(group, (a, b) => contextCompare(a, b, options)));
return function fixFunction(fixer) {
const fixers = [];
let source = sourceCode.getText();
sortableAttributeGroups.forEach((sortableGroup, ii) => {

@@ -265,9 +196,7 @@ sortableGroup.forEach((attr, jj) => {

range: [attr.range[0], attributeMap.get(attr).end],
text: sortedAttrText,
text: sortedAttrText
});
});
});
fixers.sort((a, b) => b.range[0] - a.range[0]);
const firstFixer = fixers[0];

@@ -277,43 +206,31 @@ const lastFixer = fixers[fixers.length - 1];

const rangeEnd = firstFixer ? firstFixer.range[1] : -0;
fixers.forEach((fix) => {
source = `${source.slice(0, fix.range[0])}${fix.text}${source.slice(fix.range[1])}`;
});
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd))
}
return fixer.replaceTextRange([rangeStart, rangeEnd], source.slice(rangeStart, rangeEnd));
};
}
/**
* Checks if the `reservedFirst` option is valid
* @param {object} context The context of the rule
* @param {boolean | Array<string>} reservedFirst The `reservedFirst` option
* @return {Function|undefined} If an error is detected, a function to generate the error message, otherwise, `undefined`
*/
function validateReservedFirstConfig(context, reservedFirst) {
if (reservedFirst) {
if (Array.isArray(reservedFirst)) {
// Only allow a subset of reserved words in customized lists
const nonReservedWords = reservedFirst.filter(word => !isReservedPropName(
const nonReservedWords = reservedFirst.filter((word) => !isReservedPropName(
word,
RESERVED_PROPS_LIST,
RESERVED_PROPS_LIST
));
if (reservedFirst.length === 0) {
return function Report(decl) {
utils.report(context, messages.listIsEmpty, 'listIsEmpty', {
node: decl,
utils.report(context, messages.listIsEmpty, "listIsEmpty", {
node: decl
});
}
};
}
if (nonReservedWords.length > 0) {
return function Report(decl) {
utils.report(context, messages.noUnreservedProps, 'noUnreservedProps', {
utils.report(context, messages.noUnreservedProps, "noUnreservedProps", {
node: decl,
data: {
unreservedWords: nonReservedWords.toString(),
},
unreservedWords: nonReservedWords.toString()
}
});
}
};
}

@@ -323,44 +240,25 @@ }

}
const reportedNodeAttributes = new WeakMap();
/**
* Check if the current node attribute has already been reported with the same error type
* if that's the case then we don't report a new error
* otherwise we report the error
* @param {object} nodeAttribute The node attribute to be reported
* @param {string} errorType The error type to be reported
* @param {object} node The parent node for the node attribute
* @param {object} context The context of the rule
* @param {Array<string>} reservedList The list of reserved props
*/
const reportedNodeAttributes = /* @__PURE__ */ new WeakMap();
function reportNodeAttribute(nodeAttribute, errorType, node, context, reservedList) {
const errors = reportedNodeAttributes.get(nodeAttribute) || [];
if (includes(errors, errorType))
return
return;
errors.push(errorType);
reportedNodeAttributes.set(nodeAttribute, errors);
utils.report(context, messages[errorType], errorType, {
node: nodeAttribute.name,
fix: generateFixerFunction(node, context, reservedList),
fix: generateFixerFunction(node, context, reservedList)
});
}
var jsxSortProps = {
meta: {
docs: {
description: 'Enforce props alphabetical sorting',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-sort-props'),
description: "Enforce props alphabetical sorting",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-sort-props")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
type: 'object',
type: "object",
properties: {

@@ -370,36 +268,35 @@ // Whether callbacks (prefixed with "on") should be listed at the very end,

callbacksLast: {
type: 'boolean',
type: "boolean"
},
// Whether shorthand properties (without a value) should be listed first
shorthandFirst: {
type: 'boolean',
type: "boolean"
},
// Whether shorthand properties (without a value) should be listed last
shorthandLast: {
type: 'boolean',
type: "boolean"
},
// Whether multiline properties should be listed first or last
multiline: {
enum: ['ignore', 'first', 'last'],
default: 'ignore',
enum: ["ignore", "first", "last"],
default: "ignore"
},
ignoreCase: {
type: 'boolean',
type: "boolean"
},
// Whether alphabetical sorting should be enforced
noSortAlphabetically: {
type: 'boolean',
type: "boolean"
},
reservedFirst: {
type: ['array', 'boolean'],
type: ["array", "boolean"]
},
locale: {
type: 'string',
default: 'auto',
},
type: "string",
default: "auto"
}
},
additionalProperties: false,
}],
additionalProperties: false
}]
},
create(context) {

@@ -411,3 +308,3 @@ const configuration = context.options[0] || {};

const shorthandLast = configuration.shorthandLast || false;
const multiline = configuration.multiline || 'ignore';
const multiline = configuration.multiline || "ignore";
const noSortAlphabetically = configuration.noSortAlphabetically || false;

@@ -417,17 +314,12 @@ const reservedFirst = configuration.reservedFirst || false;

const reservedList = Array.isArray(reservedFirst) ? reservedFirst : RESERVED_PROPS_LIST;
const locale = configuration.locale || 'auto';
const locale = configuration.locale || "auto";
return {
Program() {
attributeMap = new WeakMap();
attributeMap = /* @__PURE__ */ new WeakMap();
},
JSXOpeningElement(node) {
// `dangerouslySetInnerHTML` is only "reserved" on DOM components
const nodeReservedList = reservedFirst && !utils.isDOMComponent(node) ? reservedList.filter(prop => prop !== 'dangerouslySetInnerHTML') : reservedList;
const nodeReservedList = reservedFirst && !utils.isDOMComponent(node) ? reservedList.filter((prop) => prop !== "dangerouslySetInnerHTML") : reservedList;
node.attributes.reduce((memo, decl, idx, attrs) => {
if (decl.type === 'JSXSpreadAttribute')
return attrs[idx + 1]
if (decl.type === "JSXSpreadAttribute")
return attrs[idx + 1];
let previousPropName = utils.getPropName(memo);

@@ -439,3 +331,2 @@ let currentPropName = utils.getPropName(decl);

const currentIsCallback = isCallbackPropName(currentPropName);
if (ignoreCase) {

@@ -445,104 +336,71 @@ previousPropName = previousPropName.toLowerCase();

}
if (reservedFirst) {
if (reservedFirstError) {
reservedFirstError(decl);
return memo
return memo;
}
const previousIsReserved = isReservedPropName(previousPropName, nodeReservedList);
const currentIsReserved = isReservedPropName(currentPropName, nodeReservedList);
if (previousIsReserved && !currentIsReserved)
return decl
return decl;
if (!previousIsReserved && currentIsReserved) {
reportNodeAttribute(decl, 'listReservedPropsFirst', node, context, nodeReservedList);
return memo
reportNodeAttribute(decl, "listReservedPropsFirst", node, context, nodeReservedList);
return memo;
}
}
if (callbacksLast) {
if (!previousIsCallback && currentIsCallback) {
// Entering the callback prop section
return decl
return decl;
}
if (previousIsCallback && !currentIsCallback) {
// Encountered a non-callback prop after a callback prop
reportNodeAttribute(memo, 'listCallbacksLast', node, context, nodeReservedList);
return memo
reportNodeAttribute(memo, "listCallbacksLast", node, context, nodeReservedList);
return memo;
}
}
if (shorthandFirst) {
if (currentValue && !previousValue)
return decl
return decl;
if (!currentValue && previousValue) {
reportNodeAttribute(decl, 'listShorthandFirst', node, context, nodeReservedList);
return memo
reportNodeAttribute(decl, "listShorthandFirst", node, context, nodeReservedList);
return memo;
}
}
if (shorthandLast) {
if (!currentValue && previousValue)
return decl
return decl;
if (currentValue && !previousValue) {
reportNodeAttribute(memo, 'listShorthandLast', node, context, nodeReservedList);
return memo
reportNodeAttribute(memo, "listShorthandLast", node, context, nodeReservedList);
return memo;
}
}
const previousIsMultiline = isMultilineProp(memo);
const currentIsMultiline = isMultilineProp(decl);
if (multiline === 'first') {
if (multiline === "first") {
if (previousIsMultiline && !currentIsMultiline) {
// Exiting the multiline prop section
return decl
return decl;
}
if (!previousIsMultiline && currentIsMultiline) {
// Encountered a non-multiline prop before a multiline prop
reportNodeAttribute(decl, 'listMultilineFirst', node, context, nodeReservedList);
return memo
reportNodeAttribute(decl, "listMultilineFirst", node, context, nodeReservedList);
return memo;
}
}
else if (multiline === 'last') {
} else if (multiline === "last") {
if (!previousIsMultiline && currentIsMultiline) {
// Entering the multiline prop section
return decl
return decl;
}
if (previousIsMultiline && !currentIsMultiline) {
// Encountered a non-multiline prop after a multiline prop
reportNodeAttribute(memo, 'listMultilineLast', node, context, nodeReservedList);
return memo
reportNodeAttribute(memo, "listMultilineLast", node, context, nodeReservedList);
return memo;
}
}
if (
!noSortAlphabetically
&& (
(ignoreCase || locale !== 'auto')
? previousPropName.localeCompare(currentPropName, locale === 'auto' ? undefined : locale) > 0
: previousPropName > currentPropName
)
) {
reportNodeAttribute(decl, 'sortPropsByAlpha', node, context, nodeReservedList);
return memo
if (!noSortAlphabetically && (ignoreCase || locale !== "auto" ? previousPropName.localeCompare(currentPropName, locale === "auto" ? void 0 : locale) > 0 : previousPropName > currentPropName)) {
reportNodeAttribute(decl, "sortPropsByAlpha", node, context, nodeReservedList);
return memo;
}
return decl
return decl;
}, node.attributes[0]);
},
}
},
}
};
}
};
exports.jsxSortProps = jsxSortProps;

@@ -5,93 +5,73 @@ 'use strict';

/**
* @fileoverview Validates whitespace in and around the JSX opening and closing brackets
* @author Diogo Franco (Kovensky)
*/
const messages = {
selfCloseSlashNoSpace: 'Whitespace is forbidden between `/` and `>`; write `/>`',
selfCloseSlashNeedSpace: 'Whitespace is required between `/` and `>`; write `/ >`',
closeSlashNoSpace: 'Whitespace is forbidden between `<` and `/`; write `</`',
closeSlashNeedSpace: 'Whitespace is required between `<` and `/`; write `< /`',
beforeSelfCloseNoSpace: 'A space is forbidden before closing bracket',
beforeSelfCloseNeedSpace: 'A space is required before closing bracket',
beforeSelfCloseNeedNewline: 'A newline is required before closing bracket',
afterOpenNoSpace: 'A space is forbidden after opening bracket',
afterOpenNeedSpace: 'A space is required after opening bracket',
beforeCloseNoSpace: 'A space is forbidden before closing bracket',
beforeCloseNeedSpace: 'Whitespace is required before closing bracket',
beforeCloseNeedNewline: 'A newline is required before closing bracket',
selfCloseSlashNoSpace: "Whitespace is forbidden between `/` and `>`; write `/>`",
selfCloseSlashNeedSpace: "Whitespace is required between `/` and `>`; write `/ >`",
closeSlashNoSpace: "Whitespace is forbidden between `<` and `/`; write `</`",
closeSlashNeedSpace: "Whitespace is required between `<` and `/`; write `< /`",
beforeSelfCloseNoSpace: "A space is forbidden before closing bracket",
beforeSelfCloseNeedSpace: "A space is required before closing bracket",
beforeSelfCloseNeedNewline: "A newline is required before closing bracket",
afterOpenNoSpace: "A space is forbidden after opening bracket",
afterOpenNeedSpace: "A space is required after opening bracket",
beforeCloseNoSpace: "A space is forbidden before closing bracket",
beforeCloseNeedSpace: "Whitespace is required before closing bracket",
beforeCloseNeedNewline: "A newline is required before closing bracket"
};
// ------------------------------------------------------------------------------
// Validators
// ------------------------------------------------------------------------------
function validateClosingSlash(context, node, option) {
const sourceCode = context.getSourceCode();
let adjacent;
if (node.selfClosing) {
const lastTokens = sourceCode.getLastTokens(node, 2);
adjacent = !sourceCode.isSpaceBetweenTokens(lastTokens[0], lastTokens[1]);
if (option === 'never') {
if (option === "never") {
if (!adjacent) {
utils.report(context, messages.selfCloseSlashNoSpace, 'selfCloseSlashNoSpace', {
utils.report(context, messages.selfCloseSlashNoSpace, "selfCloseSlashNoSpace", {
node,
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end,
end: lastTokens[1].loc.end
},
fix(fixer) {
return fixer.removeRange([lastTokens[0].range[1], lastTokens[1].range[0]])
},
return fixer.removeRange([lastTokens[0].range[1], lastTokens[1].range[0]]);
}
});
}
}
else if (option === 'always' && adjacent) {
utils.report(context, messages.selfCloseSlashNeedSpace, 'selfCloseSlashNeedSpace', {
} else if (option === "always" && adjacent) {
utils.report(context, messages.selfCloseSlashNeedSpace, "selfCloseSlashNeedSpace", {
node,
loc: {
start: lastTokens[0].loc.start,
end: lastTokens[1].loc.end,
end: lastTokens[1].loc.end
},
fix(fixer) {
return fixer.insertTextBefore(lastTokens[1], ' ')
},
return fixer.insertTextBefore(lastTokens[1], " ");
}
});
}
}
else {
} else {
const firstTokens = sourceCode.getFirstTokens(node, 2);
adjacent = !sourceCode.isSpaceBetweenTokens(firstTokens[0], firstTokens[1]);
if (option === 'never') {
if (option === "never") {
if (!adjacent) {
utils.report(context, messages.closeSlashNoSpace, 'closeSlashNoSpace', {
utils.report(context, messages.closeSlashNoSpace, "closeSlashNoSpace", {
node,
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end,
end: firstTokens[1].loc.end
},
fix(fixer) {
return fixer.removeRange([firstTokens[0].range[1], firstTokens[1].range[0]])
},
return fixer.removeRange([firstTokens[0].range[1], firstTokens[1].range[0]]);
}
});
}
}
else if (option === 'always' && adjacent) {
utils.report(context, messages.closeSlashNeedSpace, 'closeSlashNeedSpace', {
} else if (option === "always" && adjacent) {
utils.report(context, messages.closeSlashNeedSpace, "closeSlashNeedSpace", {
node,
loc: {
start: firstTokens[0].loc.start,
end: firstTokens[1].loc.end,
end: firstTokens[1].loc.end
},
fix(fixer) {
return fixer.insertTextBefore(firstTokens[1], ' ')
},
return fixer.insertTextBefore(firstTokens[1], " ");
}
});

@@ -101,3 +81,2 @@ }

}
function validateBeforeSelfClosing(context, node, option) {

@@ -107,32 +86,27 @@ const sourceCode = context.getSourceCode();

const closingSlash = sourceCode.getTokenAfter(leftToken);
if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
if (node.loc.start.line !== node.loc.end.line && option === "proportional-always") {
if (leftToken.loc.end.line === closingSlash.loc.start.line) {
utils.report(context, messages.beforeSelfCloseNeedNewline, 'beforeSelfCloseNeedNewline', {
utils.report(context, messages.beforeSelfCloseNeedNewline, "beforeSelfCloseNeedNewline", {
node,
loc: leftToken.loc.end,
fix(fixer) {
return fixer.insertTextBefore(closingSlash, '\n')
},
return fixer.insertTextBefore(closingSlash, "\n");
}
});
return
return;
}
}
if (leftToken.loc.end.line !== closingSlash.loc.start.line)
return
return;
const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingSlash);
if ((option === 'always' || option === 'proportional-always') && adjacent) {
utils.report(context, messages.beforeSelfCloseNeedSpace, 'beforeSelfCloseNeedSpace', {
if ((option === "always" || option === "proportional-always") && adjacent) {
utils.report(context, messages.beforeSelfCloseNeedSpace, "beforeSelfCloseNeedSpace", {
node,
loc: closingSlash.loc.start,
fix(fixer) {
return fixer.insertTextBefore(closingSlash, ' ')
},
return fixer.insertTextBefore(closingSlash, " ");
}
});
}
else if (option === 'never' && !adjacent) {
utils.report(context, messages.beforeSelfCloseNoSpace, 'beforeSelfCloseNoSpace', {
} else if (option === "never" && !adjacent) {
utils.report(context, messages.beforeSelfCloseNoSpace, "beforeSelfCloseNoSpace", {
node,

@@ -142,108 +116,93 @@ loc: closingSlash.loc.start,

const previousToken = sourceCode.getTokenBefore(closingSlash);
return fixer.removeRange([previousToken.range[1], closingSlash.range[0]])
},
return fixer.removeRange([previousToken.range[1], closingSlash.range[0]]);
}
});
}
}
function validateAfterOpening(context, node, option) {
const sourceCode = context.getSourceCode();
const openingToken = sourceCode.getTokenBefore(node.name);
if (option === 'allow-multiline') {
if (option === "allow-multiline") {
if (openingToken.loc.start.line !== node.name.loc.start.line)
return
return;
}
const adjacent = !sourceCode.isSpaceBetweenTokens(openingToken, node.name);
if (option === 'never' || option === 'allow-multiline') {
if (option === "never" || option === "allow-multiline") {
if (!adjacent) {
utils.report(context, messages.afterOpenNoSpace, 'afterOpenNoSpace', {
utils.report(context, messages.afterOpenNoSpace, "afterOpenNoSpace", {
node,
loc: {
start: openingToken.loc.start,
end: node.name.loc.start,
end: node.name.loc.start
},
fix(fixer) {
return fixer.removeRange([openingToken.range[1], node.name.range[0]])
},
return fixer.removeRange([openingToken.range[1], node.name.range[0]]);
}
});
}
}
else if (option === 'always' && adjacent) {
utils.report(context, messages.afterOpenNeedSpace, 'afterOpenNeedSpace', {
} else if (option === "always" && adjacent) {
utils.report(context, messages.afterOpenNeedSpace, "afterOpenNeedSpace", {
node,
loc: {
start: openingToken.loc.start,
end: node.name.loc.start,
end: node.name.loc.start
},
fix(fixer) {
return fixer.insertTextBefore(node.name, ' ')
},
return fixer.insertTextBefore(node.name, " ");
}
});
}
}
function validateBeforeClosing(context, node, option) {
// Don't enforce this rule for self closing tags
if (!node.selfClosing) {
const sourceCode = context.getSourceCode();
const leftToken = option === 'proportional-always'
? utils.getTokenBeforeClosingBracket(node)
: sourceCode.getLastTokens(node, 2)[0];
const leftToken = option === "proportional-always" ? utils.getTokenBeforeClosingBracket(node) : sourceCode.getLastTokens(node, 2)[0];
const closingToken = sourceCode.getTokenAfter(leftToken);
if (node.loc.start.line !== node.loc.end.line && option === 'proportional-always') {
if (node.loc.start.line !== node.loc.end.line && option === "proportional-always") {
if (leftToken.loc.end.line === closingToken.loc.start.line) {
utils.report(context, messages.beforeCloseNeedNewline, 'beforeCloseNeedNewline', {
utils.report(context, messages.beforeCloseNeedNewline, "beforeCloseNeedNewline", {
node,
loc: leftToken.loc.end,
fix(fixer) {
return fixer.insertTextBefore(closingToken, '\n')
},
return fixer.insertTextBefore(closingToken, "\n");
}
});
return
return;
}
}
if (leftToken.loc.start.line !== closingToken.loc.start.line)
return
return;
const adjacent = !sourceCode.isSpaceBetweenTokens(leftToken, closingToken);
if (option === 'never' && !adjacent) {
utils.report(context, messages.beforeCloseNoSpace, 'beforeCloseNoSpace', {
if (option === "never" && !adjacent) {
utils.report(context, messages.beforeCloseNoSpace, "beforeCloseNoSpace", {
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start,
end: closingToken.loc.start
},
fix(fixer) {
return fixer.removeRange([leftToken.range[1], closingToken.range[0]])
},
return fixer.removeRange([leftToken.range[1], closingToken.range[0]]);
}
});
}
else if (option === 'always' && adjacent) {
utils.report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
} else if (option === "always" && adjacent) {
utils.report(context, messages.beforeCloseNeedSpace, "beforeCloseNeedSpace", {
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start,
end: closingToken.loc.start
},
fix(fixer) {
return fixer.insertTextBefore(closingToken, ' ')
},
return fixer.insertTextBefore(closingToken, " ");
}
});
}
else if (option === 'proportional-always' && node.type === 'JSXOpeningElement' && adjacent !== (node.loc.start.line === node.loc.end.line)) {
utils.report(context, messages.beforeCloseNeedSpace, 'beforeCloseNeedSpace', {
} else if (option === "proportional-always" && node.type === "JSXOpeningElement" && adjacent !== (node.loc.start.line === node.loc.end.line)) {
utils.report(context, messages.beforeCloseNeedSpace, "beforeCloseNeedSpace", {
node,
loc: {
start: leftToken.loc.end,
end: closingToken.loc.start,
end: closingToken.loc.start
},
fix(fixer) {
return fixer.insertTextBefore(closingToken, ' ')
},
return fixer.insertTextBefore(closingToken, " ");
}
});

@@ -253,79 +212,64 @@ }

}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const optionDefaults = {
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never',
beforeClosing: 'allow',
closingSlash: "never",
beforeSelfClosing: "always",
afterOpening: "never",
beforeClosing: "allow"
};
var jsxTagSpacing = {
meta: {
docs: {
description: 'Enforce whitespace in and around the JSX opening and closing brackets',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-tag-spacing'),
description: "Enforce whitespace in and around the JSX opening and closing brackets",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-tag-spacing")
},
fixable: 'whitespace',
fixable: "whitespace",
messages,
schema: [
{
type: 'object',
type: "object",
properties: {
closingSlash: {
enum: ['always', 'never', 'allow'],
enum: ["always", "never", "allow"]
},
beforeSelfClosing: {
enum: ['always', 'proportional-always', 'never', 'allow'],
enum: ["always", "proportional-always", "never", "allow"]
},
afterOpening: {
enum: ['always', 'allow-multiline', 'never', 'allow'],
enum: ["always", "allow-multiline", "never", "allow"]
},
beforeClosing: {
enum: ['always', 'proportional-always', 'never', 'allow'],
},
enum: ["always", "proportional-always", "never", "allow"]
}
},
default: optionDefaults,
additionalProperties: false,
},
],
additionalProperties: false
}
]
},
create(context) {
const options = Object.assign({}, optionDefaults, context.options[0]);
return {
JSXOpeningElement(node) {
if (options.closingSlash !== 'allow' && node.selfClosing)
if (options.closingSlash !== "allow" && node.selfClosing)
validateClosingSlash(context, node, options.closingSlash);
if (options.afterOpening !== 'allow')
if (options.afterOpening !== "allow")
validateAfterOpening(context, node, options.afterOpening);
if (options.beforeSelfClosing !== 'allow' && node.selfClosing)
if (options.beforeSelfClosing !== "allow" && node.selfClosing)
validateBeforeSelfClosing(context, node, options.beforeSelfClosing);
if (options.beforeClosing !== 'allow')
if (options.beforeClosing !== "allow")
validateBeforeClosing(context, node, options.beforeClosing);
},
JSXClosingElement(node) {
if (options.afterOpening !== 'allow')
if (options.afterOpening !== "allow")
validateAfterOpening(context, node, options.afterOpening);
if (options.closingSlash !== 'allow')
if (options.closingSlash !== "allow")
validateClosingSlash(context, node, options.closingSlash);
if (options.beforeClosing !== 'allow')
if (options.beforeClosing !== "allow")
validateBeforeClosing(context, node, options.beforeClosing);
},
}
},
}
};
}
};
exports.jsxTagSpacing = jsxTagSpacing;

@@ -5,75 +5,54 @@ 'use strict';

/**
* @fileoverview Prevent missing parentheses around multilines JSX
* @author Yannick Croissant
*/
const has = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const DEFAULTS = {
declaration: 'parens',
assignment: 'parens',
return: 'parens',
arrow: 'parens',
condition: 'ignore',
logical: 'ignore',
prop: 'ignore',
declaration: "parens",
assignment: "parens",
return: "parens",
arrow: "parens",
condition: "ignore",
logical: "ignore",
prop: "ignore"
};
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
missingParens: 'Missing parentheses around multilines JSX',
parensOnNewLines: 'Parentheses around JSX should be on separate lines',
missingParens: "Missing parentheses around multilines JSX",
parensOnNewLines: "Parentheses around JSX should be on separate lines"
};
var jsxWrapMultilines = {
meta: {
docs: {
description: 'Disallow missing parentheses around multiline JSX',
category: 'Stylistic Issues',
recommended: false,
url: utils.docsUrl('jsx-wrap-multilines'),
description: "Disallow missing parentheses around multiline JSX",
category: "Stylistic Issues",
url: utils.docsUrl("jsx-wrap-multilines")
},
fixable: 'code',
fixable: "code",
messages,
schema: [{
type: 'object',
type: "object",
// true/false are for backwards compatibility
properties: {
declaration: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
enum: [true, false, "ignore", "parens", "parens-new-line"]
},
assignment: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
enum: [true, false, "ignore", "parens", "parens-new-line"]
},
return: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
enum: [true, false, "ignore", "parens", "parens-new-line"]
},
arrow: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
enum: [true, false, "ignore", "parens", "parens-new-line"]
},
condition: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
enum: [true, false, "ignore", "parens", "parens-new-line"]
},
logical: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
enum: [true, false, "ignore", "parens", "parens-new-line"]
},
prop: {
enum: [true, false, 'ignore', 'parens', 'parens-new-line'],
},
enum: [true, false, "ignore", "parens", "parens-new-line"]
}
},
additionalProperties: false,
}],
additionalProperties: false
}]
},
create(context) {

@@ -83,65 +62,46 @@ function getOption(type) {

if (has(userOptions, type))
return userOptions[type]
return DEFAULTS[type]
return userOptions[type];
return DEFAULTS[type];
}
function isEnabled(type) {
const option = getOption(type);
return option && option !== 'ignore'
return option && option !== "ignore";
}
function needsOpeningNewLine(node) {
const previousToken = context.getSourceCode().getTokenBefore(node);
if (!utils.isParenthesized(context, node))
return false
return false;
if (previousToken.loc.end.line === node.loc.start.line)
return true
return false
return true;
return false;
}
function needsClosingNewLine(node) {
const nextToken = context.getSourceCode().getTokenAfter(node);
if (!utils.isParenthesized(context, node))
return false
return false;
if (node.loc.end.line === nextToken.loc.end.line)
return true
return false
return true;
return false;
}
function isMultilines(node) {
return node.loc.start.line !== node.loc.end.line
return node.loc.start.line !== node.loc.end.line;
}
function report(node, messageId, fix) {
utils.report(context, messages[messageId], messageId, {
node,
fix,
fix
});
}
function trimTokenBeforeNewline(node, tokenBefore) {
// if the token before the jsx is a bracket or curly brace
// we don't want a space between the opening parentheses and the multiline jsx
const isBracket = tokenBefore.value === '{' || tokenBefore.value === '[';
return `${tokenBefore.value.trim()}${isBracket ? '' : ' '}`
const isBracket = tokenBefore.value === "{" || tokenBefore.value === "[";
return `${tokenBefore.value.trim()}${isBracket ? "" : " "}`;
}
function check(node, type) {
if (!node || !utils.isJSX(node))
return
return;
const sourceCode = context.getSourceCode();
const option = getOption(type);
if ((option === true || option === 'parens') && !utils.isParenthesized(context, node) && isMultilines(node))
report(node, 'missingParens', fixer => fixer.replaceText(node, `(${sourceCode.getText(node)})`));
if (option === 'parens-new-line' && isMultilines(node)) {
if ((option === true || option === "parens") && !utils.isParenthesized(context, node) && isMultilines(node))
report(node, "missingParens", (fixer) => fixer.replaceText(node, `(${sourceCode.getText(node)})`));
if (option === "parens-new-line" && isMultilines(node)) {
if (!utils.isParenthesized(context, node)) {

@@ -152,30 +112,31 @@ const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });

if (tokenBefore.loc.end.line < start.line) {
// Strip newline after operator if parens newline is specified
report(
node,
'missingParens',
fixer => fixer.replaceTextRange(
[tokenBefore.range[0], tokenAfter && (tokenAfter.value === ';' || tokenAfter.value === '}') ? tokenAfter.range[0] : node.range[1]],
`${trimTokenBeforeNewline(node, tokenBefore)}(\n${start.column > 0 ? ' '.repeat(start.column) : ''}${sourceCode.getText(node)}\n${start.column > 0 ? ' '.repeat(start.column - 2) : ''})`,
),
"missingParens",
(fixer) => fixer.replaceTextRange(
[tokenBefore.range[0], tokenAfter && (tokenAfter.value === ";" || tokenAfter.value === "}") ? tokenAfter.range[0] : node.range[1]],
`${trimTokenBeforeNewline(node, tokenBefore)}(
${start.column > 0 ? " ".repeat(start.column) : ""}${sourceCode.getText(node)}
${start.column > 0 ? " ".repeat(start.column - 2) : ""})`
)
);
} else {
report(node, "missingParens", (fixer) => fixer.replaceText(node, `(
${sourceCode.getText(node)}
)`));
}
else {
report(node, 'missingParens', fixer => fixer.replaceText(node, `(\n${sourceCode.getText(node)}\n)`));
}
}
else {
} else {
const needsOpening = needsOpeningNewLine(node);
const needsClosing = needsClosingNewLine(node);
if (needsOpening || needsClosing) {
report(node, 'parensOnNewLines', (fixer) => {
report(node, "parensOnNewLines", (fixer) => {
const text = sourceCode.getText(node);
let fixed = text;
if (needsOpening)
fixed = `\n${fixed}`;
fixed = `
${fixed}`;
if (needsClosing)
fixed = `${fixed}\n`;
return fixer.replaceText(node, fixed)
fixed = `${fixed}
`;
return fixer.replaceText(node, fixed);
});

@@ -186,51 +147,38 @@ }

}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
VariableDeclarator(node) {
const type = 'declaration';
const type = "declaration";
if (!isEnabled(type))
return
if (!isEnabled('condition') && node.init && node.init.type === 'ConditionalExpression') {
return;
if (!isEnabled("condition") && node.init && node.init.type === "ConditionalExpression") {
check(node.init.consequent, type);
check(node.init.alternate, type);
return
return;
}
check(node.init, type);
},
AssignmentExpression(node) {
const type = 'assignment';
const type = "assignment";
if (!isEnabled(type))
return
if (!isEnabled('condition') && node.right.type === 'ConditionalExpression') {
return;
if (!isEnabled("condition") && node.right.type === "ConditionalExpression") {
check(node.right.consequent, type);
check(node.right.alternate, type);
return
return;
}
check(node.right, type);
},
ReturnStatement(node) {
const type = 'return';
const type = "return";
if (isEnabled(type))
check(node.argument, type);
},
'ArrowFunctionExpression:exit': (node) => {
"ArrowFunctionExpression:exit": (node) => {
const arrowBody = node.body;
const type = 'arrow';
if (isEnabled(type) && arrowBody.type !== 'BlockStatement')
const type = "arrow";
if (isEnabled(type) && arrowBody.type !== "BlockStatement")
check(arrowBody, type);
},
ConditionalExpression(node) {
const type = 'condition';
const type = "condition";
if (isEnabled(type)) {

@@ -241,18 +189,16 @@ check(node.consequent, type);

},
LogicalExpression(node) {
const type = 'logical';
const type = "logical";
if (isEnabled(type))
check(node.right, type);
},
JSXAttribute(node) {
const type = 'prop';
if (isEnabled(type) && node.value && node.value.type === 'JSXExpressionContainer')
const type = "prop";
if (isEnabled(type) && node.value && node.value.type === "JSXExpressionContainer")
check(node.value.expression, type);
},
}
},
}
};
}
};
exports.jsxWrapMultilines = jsxWrapMultilines;

@@ -5,8 +5,12 @@ 'use strict';

function createRule(rule) {
return rule;
}
function docsUrl(ruleName) {
return `https://eslint.style/rules/jsx/${ruleName}`
return `https://eslint.style/rules/jsx/${ruleName}`;
}
function getMessageData(messageId) {
return { messageId }
return { messageId };
}

@@ -18,81 +22,33 @@

getMessageData(messageId),
data,
),
data
)
);
}
/**
* @fileoverview Utility functions for AST
*/
/**
* Wrapper for estraverse.traverse
*
* @param {ASTNode} ASTnode The AST node being checked
* @param {object} visitor Visitor Object for estraverse
*/
function traverse(ASTnode, visitor) {
const opts = Object.assign({}, {
fallback(node) {
return Object.keys(node).filter(key => key === 'children' || key === 'argument')
},
return Object.keys(node).filter((key) => key === "children" || key === "argument");
}
}, visitor);
opts.keys = Object.assign({}, visitor.keys, {
JSXElement: ['children'],
JSXFragment: ['children'],
JSXElement: ["children"],
JSXFragment: ["children"]
});
estraverse.traverse(ASTnode, opts);
}
/**
* Helper function for traversing "returns" (return statements or the
* returned expression in the case of an arrow function) of a function
*
* @param {ASTNode} ASTNode The AST node being checked
* @param {Context} context The context of `ASTNode`.
* @param {(returnValue: ASTNode, breakTraverse: () => void) => void} onReturn
* Function to execute for each returnStatement found
* @returns {undefined}
*/
function traverseReturns(ASTNode, context, onReturn) {
const nodeType = ASTNode.type;
if (nodeType === 'ReturnStatement') {
onReturn(ASTNode.argument, () => {});
return
if (nodeType === "ReturnStatement") {
onReturn(ASTNode.argument, () => {
});
return;
}
if (nodeType === 'ArrowFunctionExpression' && ASTNode.expression) {
onReturn(ASTNode.body, () => {});
return
if (nodeType === "ArrowFunctionExpression" && ASTNode.expression) {
onReturn(ASTNode.body, () => {
});
return;
}
/* TODO: properly warn on React.forwardRefs having typo properties
if (nodeType === 'CallExpression') {
const callee = ASTNode.callee;
const pragma = pragmaUtil.getFromContext(context);
if (
callee.type === 'MemberExpression'
&& callee.object.type === 'Identifier'
&& callee.object.name === pragma
&& callee.property.type === 'Identifier'
&& callee.property.name === 'forwardRef'
&& ASTNode.arguments.length > 0
) {
return enterFunc(ASTNode.arguments[0]);
}
if (nodeType !== "FunctionExpression" && nodeType !== "FunctionDeclaration" && nodeType !== "ArrowFunctionExpression" && nodeType !== "MethodDefinition")
return;
}
*/
if (
nodeType !== 'FunctionExpression'
&& nodeType !== 'FunctionDeclaration'
&& nodeType !== 'ArrowFunctionExpression'
&& nodeType !== 'MethodDefinition'
)
return
traverse(ASTNode.body, {

@@ -104,26 +60,19 @@ enter(node) {

switch (node.type) {
case 'ReturnStatement':
case "ReturnStatement":
this.skip();
onReturn(node.argument, breakTraverse);
return
case 'BlockStatement':
case 'IfStatement':
case 'ForStatement':
case 'WhileStatement':
case 'SwitchStatement':
case 'SwitchCase':
return
return;
case "BlockStatement":
case "IfStatement":
case "ForStatement":
case "WhileStatement":
case "SwitchStatement":
case "SwitchCase":
return;
default:
this.skip();
}
},
}
});
}
/**
* Gets the first node in a line from the initial node, excluding whitespace.
* @param {object} context The node to check
* @param {ASTNode} node The node to check
* @return {ASTNode} the first node in the line
*/
function getFirstNodeInLine(context, node) {

@@ -135,18 +84,6 @@ const sourceCode = context.getSourceCode();

token = sourceCode.getTokenBefore(token);
lines = token.type === 'JSXText'
? token.value.split('\n')
: null;
} while (
token.type === 'JSXText'
&& /^\s*$/.test(lines[lines.length - 1])
)
return token
lines = token.type === "JSXText" ? token.value.split("\n") : null;
} while (token.type === "JSXText" && /^\s*$/.test(lines[lines.length - 1]));
return token;
}
/**
* Checks if the node is the first in its line, excluding whitespace.
* @param {object} context The node to check
* @param {ASTNode} node The node to check
* @return {boolean} true if it's the first node in its line
*/
function isNodeFirstInLine(context, node) {

@@ -156,12 +93,4 @@ const token = getFirstNodeInLine(context, node);

const endLine = token ? token.loc.end.line : -1;
return startLine !== endLine
return startLine !== endLine;
}
/**
* Checks if a node is surrounded by parenthesis.
*
* @param {object} context - Context from the rule
* @param {ASTNode} node - Node to be checked
* @returns {boolean}
*/
function isParenthesized(context, node) {

@@ -171,70 +100,29 @@ const sourceCode = context.getSourceCode();

const nextToken = sourceCode.getTokenAfter(node);
return !!previousToken && !!nextToken
&& previousToken.value === '(' && previousToken.range[1] <= node.range[0]
&& nextToken.value === ')' && nextToken.range[0] >= node.range[1]
return !!previousToken && !!nextToken && previousToken.value === "(" && previousToken.range[1] <= node.range[0] && nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
/**
* @fileoverview Utility functions for React pragma configuration
* @author Yannick Croissant
*/
const JSX_ANNOTATION_REGEX = /@jsx\s+([^\s]+)/;
// Does not check for reserved keywords or unicode characters
const JS_IDENTIFIER_REGEX = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
/**
* @param {Context} context
* @returns {string}
*/
function getFromContext(context) {
let pragma = 'React';
let pragma = "React";
const sourceCode = context.getSourceCode();
const pragmaNode = sourceCode.getAllComments().find(node => JSX_ANNOTATION_REGEX.test(node.value));
const pragmaNode = sourceCode.getAllComments().find((node) => JSX_ANNOTATION_REGEX.test(node.value));
if (pragmaNode) {
const matches = JSX_ANNOTATION_REGEX.exec(pragmaNode.value);
pragma = matches[1].split('.')[0];
// .eslintrc shared settings (https://eslint.org/docs/user-guide/configuring#adding-shared-settings)
}
else if (context.settings.react && context.settings.react.pragma) {
pragma = matches[1].split(".")[0];
} else if (context.settings.react && context.settings.react.pragma) {
pragma = context.settings.react.pragma;
}
if (!JS_IDENTIFIER_REGEX.test(pragma))
throw new Error(`React pragma ${pragma} is not a valid identifier`)
return pragma
throw new Error(`React pragma ${pragma} is not a valid identifier`);
return pragma;
}
/**
* @fileoverview Utility functions for React components detection
* @author Yannick Croissant
*/
/**
* Find and return a particular variable in a list
* @param {Array} variables The variables list.
* @param {string} name The name of the variable to search.
* @returns {object} Variable if the variable was found, null if not.
*/
function getVariable(variables, name) {
return variables.find(variable => variable.name === name)
return variables.find((variable) => variable.name === name);
}
/**
* List all variable in a given scope
*
* Contain a patch for babel-eslint to avoid https://github.com/babel/babel-eslint/issues/21
*
* @param {object} context The current rule context.
* @returns {Array} The variables list
*/
function variablesInScope(context) {
let scope = context.getScope();
let variables = scope.variables;
while (scope.type !== 'global') {
while (scope.type !== "global") {
scope = scope.upper;

@@ -249,43 +137,18 @@ variables = scope.variables.concat(variables);

variables.reverse();
return variables
return variables;
}
/**
* Find a variable by name in the current scope.
* @param {object} context The current rule context.
* @param {string} name Name of the variable to look for.
* @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
*/
function findVariableByName(context, name) {
const variable = getVariable(variablesInScope(context), name);
if (!variable || !variable.defs[0] || !variable.defs[0].node)
return null
if (variable.defs[0].node.type === 'TypeAlias')
return variable.defs[0].node.right
if (variable.defs[0].type === 'ImportBinding')
return variable.defs[0].node
return variable.defs[0].node.init
return null;
if (variable.defs[0].node.type === "TypeAlias")
return variable.defs[0].node.right;
if (variable.defs[0].type === "ImportBinding")
return variable.defs[0].node;
return variable.defs[0].node.init;
}
/**
* Returns the latest definition of the variable.
* @param {object} variable
* @returns {object | undefined} The latest variable definition or undefined.
*/
function getLatestVariableDefinition(variable) {
return variable.defs[variable.defs.length - 1]
return variable.defs[variable.defs.length - 1];
}
/**
* Check if variable is destructured from pragma import
*
* @param {string} variable The variable name to check
* @param {Context} context eslint context
* @returns {boolean} True if createElement is destructured from the pragma
*/
function isDestructuredFromPragmaImport(variable, context) {

@@ -298,168 +161,74 @@ const pragma = getFromContext(context);

if (latestDef) {
// check if latest definition is a variable declaration: 'variable = value'
if (latestDef.node.type === 'VariableDeclarator' && latestDef.node.init) {
// check for: 'variable = pragma.variable'
if (
latestDef.node.init.type === 'MemberExpression'
&& latestDef.node.init.object.type === 'Identifier'
&& latestDef.node.init.object.name === pragma
)
return true
// check for: '{variable} = pragma'
if (
latestDef.node.init.type === 'Identifier'
&& latestDef.node.init.name === pragma
)
return true
// "require('react')"
if (latestDef.node.type === "VariableDeclarator" && latestDef.node.init) {
if (latestDef.node.init.type === "MemberExpression" && latestDef.node.init.object.type === "Identifier" && latestDef.node.init.object.name === pragma)
return true;
if (latestDef.node.init.type === "Identifier" && latestDef.node.init.name === pragma)
return true;
let requireExpression = null;
// get "require('react')" from: "{variable} = require('react')"
if (latestDef.node.init.type === 'CallExpression')
if (latestDef.node.init.type === "CallExpression")
requireExpression = latestDef.node.init;
// get "require('react')" from: "variable = require('react').variable"
if (
!requireExpression
&& latestDef.node.init.type === 'MemberExpression'
&& latestDef.node.init.object.type === 'CallExpression'
)
if (!requireExpression && latestDef.node.init.type === "MemberExpression" && latestDef.node.init.object.type === "CallExpression")
requireExpression = latestDef.node.init.object;
// check proper require.
if (
requireExpression
&& requireExpression.callee
&& requireExpression.callee.name === 'require'
&& requireExpression.arguments[0]
&& requireExpression.arguments[0].value === pragma.toLocaleLowerCase()
)
return true
return false
if (requireExpression && requireExpression.callee && requireExpression.callee.name === "require" && requireExpression.arguments[0] && requireExpression.arguments[0].value === pragma.toLocaleLowerCase())
return true;
return false;
}
// latest definition is an import declaration: import {<variable>} from 'react'
if (
latestDef.parent
&& latestDef.parent.type === 'ImportDeclaration'
&& latestDef.parent.source.value === pragma.toLocaleLowerCase()
)
return true
if (latestDef.parent && latestDef.parent.type === "ImportDeclaration" && latestDef.parent.source.value === pragma.toLocaleLowerCase())
return true;
}
}
return false
return false;
}
/**
* Checks if the node is a createElement call
* @param {ASTNode} node - The AST node being checked.
* @param {Context} context - The AST node being checked.
* @returns {boolean} - True if node is a createElement call object literal, False if not.
*/
function isCreateElement(node, context) {
if (
node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
&& node.callee.object
&& node.callee.object.name === getFromContext(context)
)
return true
if (
node
&& node.callee
&& node.callee.name === 'createElement'
&& isDestructuredFromPragmaImport('createElement', context)
)
return true
return false
if (node.callee && node.callee.type === "MemberExpression" && node.callee.property.name === "createElement" && node.callee.object && node.callee.object.name === getFromContext(context))
return true;
if (node && node.callee && node.callee.name === "createElement" && isDestructuredFromPragmaImport("createElement", context))
return true;
return false;
}
/**
* @fileoverview Utility functions for JSX
*/
// See https://github.com/babel/babel/blob/ce420ba51c68591e057696ef43e028f41c6e04cd/packages/babel-types/src/validators/react/isCompatTag.js
// for why we only test for the first character
const COMPAT_TAG_REGEX = /^[a-z]/;
/**
* Checks if a node represents a DOM element according to React.
* @param {object} node - JSXOpeningElement to check.
* @returns {boolean} Whether or not the node corresponds to a DOM element.
*/
function isDOMComponent(node) {
const name = getElementType(node);
return COMPAT_TAG_REGEX.test(name)
return COMPAT_TAG_REGEX.test(name);
}
/**
* Checks if a node represents a JSX element or fragment.
* @param {object} node - node to check.
* @returns {boolean} Whether or not the node if a JSX element or fragment.
*/
function isJSX(node) {
return node && ['JSXElement', 'JSXFragment'].includes(node.type)
return node && ["JSXElement", "JSXFragment"].includes(node.type);
}
/**
* Check if value has only whitespaces
* @param {string} value
* @returns {boolean}
*/
function isWhiteSpaces(value) {
return typeof value === 'string' ? /^\s*$/.test(value) : false
return typeof value === "string" ? /^\s*$/.test(value) : false;
}
/**
* Check if the node is returning JSX or null
*
* @param {ASTNode} ASTnode The AST node being checked
* @param {Context} context The context of `ASTNode`.
* @param {boolean} [strict] If true, in a ternary condition the node must return JSX in both cases
* @param {boolean} [ignoreNull] If true, null return values will be ignored
* @returns {boolean} True if the node is returning JSX or null, false if not
*/
function isReturningJSX(ASTnode, context, strict, ignoreNull) {
const isJSXValue = (node) => {
if (!node)
return false
return false;
switch (node.type) {
case 'ConditionalExpression':
case "ConditionalExpression":
if (strict)
return isJSXValue(node.consequent) && isJSXValue(node.alternate)
return isJSXValue(node.consequent) || isJSXValue(node.alternate)
case 'LogicalExpression':
return isJSXValue(node.consequent) && isJSXValue(node.alternate);
return isJSXValue(node.consequent) || isJSXValue(node.alternate);
case "LogicalExpression":
if (strict)
return isJSXValue(node.left) && isJSXValue(node.right)
return isJSXValue(node.left) || isJSXValue(node.right)
case 'SequenceExpression':
return isJSXValue(node.expressions[node.expressions.length - 1])
case 'JSXElement':
case 'JSXFragment':
return true
case 'CallExpression':
return isCreateElement(node, context)
case 'Literal':
return isJSXValue(node.left) && isJSXValue(node.right);
return isJSXValue(node.left) || isJSXValue(node.right);
case "SequenceExpression":
return isJSXValue(node.expressions[node.expressions.length - 1]);
case "JSXElement":
case "JSXFragment":
return true;
case "CallExpression":
return isCreateElement(node, context);
case "Literal":
if (!ignoreNull && node.value === null)
return true
return false
case 'Identifier': {
return true;
return false;
case "Identifier": {
const variable = findVariableByName(context, node.name);
return isJSX(variable)
return isJSX(variable);
}
default:
return false
return false;
}
};
let found = false;

@@ -472,66 +241,39 @@ traverseReturns(ASTnode, context, (node, breakTraverse) => {

});
return found
return found;
}
/**
* Returns the name of the prop given the JSXAttribute object.
*
* Ported from `jsx-ast-utils/propName` to reduce bundle size
* @see https://github.com/jsx-eslint/jsx-ast-utils/blob/main/src/propName.js
*/
function getPropName(prop = {}) {
if (!prop.type || prop.type !== 'JSXAttribute')
throw new Error('The prop must be a JSXAttribute collected by the AST parser.')
if (prop.name.type === 'JSXNamespacedName')
return `${prop.name.namespace.name}:${prop.name.name.name}`
return prop.name.name
if (!prop.type || prop.type !== "JSXAttribute")
throw new Error("The prop must be a JSXAttribute collected by the AST parser.");
if (prop.name.type === "JSXNamespacedName")
return `${prop.name.namespace.name}:${prop.name.name.name}`;
return prop.name.name;
}
function resolveMemberExpressions(object = {}, property = {}) {
if (object.type === 'JSXMemberExpression')
return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`
return `${object.name}.${property.name}`
if (object.type === "JSXMemberExpression")
return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
return `${object.name}.${property.name}`;
}
/**
* Returns the tagName associated with a JSXElement.
*
* Ported from `jsx-ast-utils/elementType` to reduce bundle size
* @see https://github.com/jsx-eslint/jsx-ast-utils/blob/main/src/elementType.js
*/
function getElementType(node = {}) {
const { name } = node;
if (node.type === 'JSXOpeningFragment')
return '<>'
if (node.type === "JSXOpeningFragment")
return "<>";
if (!name)
throw new Error('The argument provided is not a JSXElement node.')
if (name.type === 'JSXMemberExpression') {
throw new Error("The argument provided is not a JSXElement node.");
if (name.type === "JSXMemberExpression") {
const { object = {}, property = {} } = name;
return resolveMemberExpressions(object, property)
return resolveMemberExpressions(object, property);
}
if (name.type === 'JSXNamespacedName')
return `${name.namespace.name}:${name.name.name}`
return node.name.name
if (name.type === "JSXNamespacedName")
return `${name.namespace.name}:${name.name.name}`;
return node.name.name;
}
/**
* Find the token before the closing bracket.
* @param {ASTNode} node - The JSX element node.
* @returns {Token} The token before the closing bracket.
*/
function getTokenBeforeClosingBracket(node) {
const attributes = node.attributes;
if (!attributes || attributes.length === 0)
return node.name
return attributes[attributes.length - 1]
return node.name;
return attributes[attributes.length - 1];
}
exports.createRule = createRule;
exports.docsUrl = docsUrl;

@@ -538,0 +280,0 @@ exports.getFirstNodeInLine = getFirstNodeInLine;

@@ -1,5 +0,2 @@

/**
* This file is GENERATED by scripts/prepare.ts
* DO NOT EDIT THIS FILE DIRECTLY
*/
/* GENERATED, DO NOT EDIT DIRECTLY */

@@ -6,0 +3,0 @@ import type { RuleOptions } from './rule-options'

@@ -1,2 +0,2 @@

import type { ESLint } from 'eslint'
import type { ESLint, Linter } from 'eslint'
import type { UnprefixedRuleOptions } from './rule-options'

@@ -6,5 +6,10 @@

export type Rules = {
[K in keyof UnprefixedRuleOptions]: ESLint.RuleModule
}
declare const plugin: {
rules: {
[K in keyof UnprefixedRuleOptions]: ESLint.RuleModule
rules: Rules
configs: {
'disable-legacy': Linter.FlatConfig
}

@@ -11,0 +16,0 @@ }

@@ -1,5 +0,2 @@

/**
* This file is GENERATED by scripts/prepare.ts
* DO NOT EDIT THIS FILE DIRECTLY
*/
/* GENERATED, DO NOT EDIT DIRECTLY */

@@ -6,0 +3,0 @@ import type { RuleOptions as JsxChildElementSpacingRuleOptions } from '../rules/jsx-child-element-spacing/types'

{
"name": "@stylistic/eslint-plugin-jsx",
"version": "1.1.0",
"version": "1.2.0",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",

@@ -55,3 +55,3 @@ "license": "MIT",

"estraverse": "^5.3.0",
"@stylistic/eslint-plugin-js": "^1.1.0"
"@stylistic/eslint-plugin-js": "^1.2.0"
},

@@ -58,0 +58,0 @@ "devDependencies": {

@@ -0,1 +1,4 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type RuleOptions = []
export type MessageIds = 'spacingAfterPrev' | 'spacingBeforeNext'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 =

@@ -12,1 +14,2 @@ | ('after-props' | 'props-aligned' | 'tag-aligned' | 'line-aligned')

export type RuleOptions = [Schema0?]
export type MessageIds = 'bracketLocation'

@@ -0,1 +1,4 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type RuleOptions = []
export type MessageIds = 'onOwnLine' | 'matchIndent'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 =

@@ -10,1 +12,2 @@ | {

export type RuleOptions = [Schema0?]
export type MessageIds = 'unnecessaryCurly' | 'missingCurly'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 =

@@ -9,1 +11,2 @@ | ('consistent' | 'never')

export type RuleOptions = [Schema0?]
export type MessageIds = 'expectedBefore' | 'expectedAfter' | 'unexpectedBefore' | 'unexpectedAfter'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 =

@@ -41,1 +43,2 @@ | []

export type RuleOptions = Schema0
export type MessageIds = 'noNewlineAfter' | 'noNewlineBefore' | 'noSpaceAfter' | 'noSpaceBefore' | 'spaceNeededAfter' | 'spaceNeededBefore'

@@ -0,3 +1,6 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 = 'always' | 'never'
export type RuleOptions = [Schema0?]
export type MessageIds = 'noSpaceBefore' | 'noSpaceAfter' | 'needSpaceBefore' | 'needSpaceAfter'

@@ -0,3 +1,6 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 = 'always' | 'never' | 'multiline' | 'multiline-multiprop' | 'multiprop'
export type RuleOptions = [Schema0?]
export type MessageIds = 'propOnNewLine' | 'propOnSameLine'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 =

@@ -11,1 +13,2 @@ | ('tab' | 'first')

export type RuleOptions = [Schema0?]
export type MessageIds = 'wrongIndent'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 = 'tab' | number

@@ -9,1 +11,2 @@

export type RuleOptions = [Schema0?, Schema1?]
export type MessageIds = 'wrongIndent'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type Schema0 =

@@ -15,1 +17,2 @@ | {

export type RuleOptions = [Schema0?]
export type MessageIds = 'newLine'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export interface Schema0 {

@@ -7,1 +9,2 @@ prevent?: boolean

export type RuleOptions = [Schema0?]
export type MessageIds = 'require' | 'prevent' | 'allowMultilines'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export interface Schema0 {

@@ -6,1 +8,2 @@ allow?: 'none' | 'literal' | 'single-child'

export type RuleOptions = [Schema0?]
export type MessageIds = 'moveToNewLine'

@@ -0,1 +1,4 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export type RuleOptions = []
export type MessageIds = 'noLineGap' | 'onlyOneSpace'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export interface Schema0 {

@@ -7,1 +9,2 @@ component?: boolean

export type RuleOptions = [Schema0?]
export type MessageIds = 'notSelfClosing'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export interface Schema0 {

@@ -13,1 +15,2 @@ callbacksLast?: boolean

export type RuleOptions = [Schema0?]
export type MessageIds = 'noUnreservedProps' | 'listIsEmpty' | 'listReservedPropsFirst' | 'listCallbacksLast' | 'listShorthandFirst' | 'listShorthandLast' | 'listMultilineFirst' | 'listMultilineLast' | 'sortPropsByAlpha'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export interface Schema0 {

@@ -9,1 +11,2 @@ closingSlash?: 'always' | 'never' | 'allow'

export type RuleOptions = [Schema0?]
export type MessageIds = 'selfCloseSlashNoSpace' | 'selfCloseSlashNeedSpace' | 'closeSlashNoSpace' | 'closeSlashNeedSpace' | 'beforeSelfCloseNoSpace' | 'beforeSelfCloseNeedSpace' | 'beforeSelfCloseNeedNewline' | 'afterOpenNoSpace' | 'afterOpenNeedSpace' | 'beforeCloseNoSpace' | 'beforeCloseNeedSpace' | 'beforeCloseNeedNewline'

@@ -0,1 +1,3 @@

/* GENERATED, DO NOT EDIT DIRECTLY */
export interface Schema0 {

@@ -12,1 +14,2 @@ declaration?: true | false | 'ignore' | 'parens' | 'parens-new-line'

export type RuleOptions = [Schema0?]
export type MessageIds = 'missingParens' | 'parensOnNewLines'
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc