| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966 |
- module.exports = {
- "multiline-import-specifiers": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- return {
- ImportDeclaration(node) {
- const specifiers = node.specifiers.filter(
- s => s.type === "ImportSpecifier"
- );
- if (specifiers.length <= 1) return;
- const sourceCode = context.getSourceCode();
- for (let i = 0; i < specifiers.length - 1; i++) {
- const current = specifiers[i];
- const next = specifiers[i + 1];
- if (current.loc.end.line === next.loc.start.line) {
- context.report({
- node: next,
- message: "Each import specifier should be on a new line",
- fix(fixer) {
- const comma = sourceCode.getTokenBefore(next);
- return fixer.replaceTextRange(
- [
- comma.range[1],
- next.range[0]
- ],
- "\n "
- );
- }
- });
- }
- }
- const lastSpecifier = specifiers[specifiers.length - 1];
- const tokenAfter = sourceCode.getTokenAfter(lastSpecifier);
- if (tokenAfter && tokenAfter.value === ",") {
- context.report({
- node: lastSpecifier,
- message: "No trailing comma in imports",
- fix(fixer) {
- return fixer.remove(tokenAfter);
- }
- });
- }
- }
- };
- }
- },
- "multiline-object-properties": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- function checkProperties(node, properties) {
- if (properties.length === 0) return;
- const openBrace = sourceCode.getFirstToken(node);
- let closingBraceToken = sourceCode.getTokenAfter(properties[properties.length - 1]);
- while (closingBraceToken && closingBraceToken.value !== "}") {
- closingBraceToken = sourceCode.getTokenAfter(closingBraceToken);
- }
- const closingBrace = closingBraceToken;
- if (openBrace.loc.start.line === closingBrace.loc.end.line) return;
- const firstProperty = properties[0];
- if (openBrace.loc.end.line !== firstProperty.loc.start.line - 1) {
- context.report({
- node: firstProperty,
- message: "First property should be exactly on the next line",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[openBrace.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- return fixer.replaceTextRange(
- [
- openBrace.range[1],
- firstProperty.range[0]
- ],
- "\n" + indent
- );
- }
- });
- }
- if (properties.length > 1) {
- for (let i = 0; i < properties.length - 1; i++) {
- const current = properties[i];
- const next = properties[i + 1];
- if (current.loc.end.line !== next.loc.start.line - 1) {
- context.report({
- node: next,
- message: "Each property should be exactly on the next line (no empty lines)",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[openBrace.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- const comma = sourceCode.getTokenBefore(next);
- return fixer.replaceTextRange(
- [
- comma.range[1],
- next.range[0]
- ],
- "\n" + indent
- );
- }
- });
- }
- }
- }
- const lastProperty = properties[properties.length - 1];
- if (lastProperty.loc.end.line !== closingBrace.loc.start.line - 1) {
- context.report({
- node: closingBrace,
- message: "Object closing brace should be exactly on the next line",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[openBrace.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- // Be careful about trailing comma
- const tokenAfter = sourceCode.getTokenAfter(lastProperty);
- const hasTrailingComma = tokenAfter && tokenAfter.value === ",";
- const startRange = hasTrailingComma ? tokenAfter.range[1] : lastProperty.range[1];
- return fixer.replaceTextRange(
- [
- startRange,
- closingBrace.range[0]
- ],
- "\n" + baseIndent
- );
- }
- });
- }
- const tokenAfter = sourceCode.getTokenAfter(lastProperty);
- if (tokenAfter && tokenAfter.value === ",") {
- context.report({
- node: lastProperty,
- message: "No trailing comma in objects",
- fix(fixer) {
- return fixer.remove(tokenAfter);
- }
- });
- }
- }
- return {
- ObjectExpression(node) {
- checkProperties(node, node.properties);
- },
- ObjectPattern(node) {
- checkProperties(node, node.properties);
- }
- };
- }
- },
- "multiline-jsx-attributes": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- return {
- JSXOpeningElement(node) {
- if (node.attributes.length === 0) return;
- const firstToken = sourceCode.getFirstToken(node);
- const tagNameToken = sourceCode.getTokenAfter(firstToken);
- const firstAttr = node.attributes[0];
- if (tagNameToken.loc.end.line !== firstAttr.loc.start.line - 1) {
- context.report({
- node: firstAttr,
- message: "First JSX attribute should be exactly on the next line",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- return fixer.replaceTextRange(
- [
- tagNameToken.range[1],
- firstAttr.range[0]
- ],
- "\n" + indent
- );
- }
- });
- }
- for (let i = 0; i < node.attributes.length - 1; i++) {
- const current = node.attributes[i];
- const next = node.attributes[i + 1];
- if (current.loc.end.line !== next.loc.start.line - 1) {
- context.report({
- node: next,
- message: "Each JSX attribute should be exactly on the next line (no empty lines)",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- return fixer.replaceTextRange(
- [
- current.range[1],
- next.range[0]
- ],
- "\n" + indent
- );
- }
- });
- }
- }
- const lastAttr = node.attributes[node.attributes.length - 1];
- const allTokens = [];
- let currentToken = sourceCode.getTokenAfter(lastAttr);
- while (currentToken && currentToken.range[1] <= node.range[1]) {
- allTokens.push(currentToken);
- currentToken = sourceCode.getTokenAfter(currentToken);
- }
- const closingBracket = allTokens[allTokens.length - 1];
- // Check if self-closing slash is present
- let hasSelfClosingSlash = false;
- let slashToken = null;
- if (node.selfClosing && allTokens.length >= 2) {
- slashToken = allTokens[allTokens.length - 2];
- if (slashToken && slashToken.value === "/") {
- hasSelfClosingSlash = true;
- }
- }
- if (hasSelfClosingSlash) {
- if (lastAttr.loc.end.line !== slashToken.loc.start.line - 1) {
- context.report({
- node: slashToken,
- message: "JSX closing slash should be exactly on the next line",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- return fixer.replaceTextRange(
- [
- lastAttr.range[1],
- slashToken.range[0]
- ],
- "\n" + baseIndent
- );
- }
- });
- }
- } else {
- if (lastAttr.loc.end.line !== closingBracket.loc.start.line - 1) {
- context.report({
- node: closingBracket,
- message: "JSX closing bracket should be exactly on the next line",
- fix(fixer) {
- const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
- const baseIndent = openBraceLine.match(/^\s*/)[0];
- return fixer.replaceTextRange(
- [
- lastAttr.range[1],
- closingBracket.range[0]
- ],
- "\n" + baseIndent
- );
- }
- });
- }
- }
- }
- };
- }
- },
- "multiline-array-elements": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- return {
- ArrayExpression(node) {
- if (node.elements.length === 0) return;
- const openBracket = sourceCode.getFirstToken(node);
- const closingBracket = sourceCode.getLastToken(node);
- if (openBracket.loc.start.line === closingBracket.loc.end.line) return;
- const firstElement = node.elements[0];
- if (openBracket.loc.end.line !== firstElement.loc.start.line - 1) {
- context.report({
- node: firstElement,
- message: "First array element should be exactly on the next line",
- fix(fixer) {
- const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
- const baseIndent = openBracketLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- return fixer.replaceTextRange(
- [
- openBracket.range[1],
- firstElement.range[0]
- ],
- "\n" + indent
- );
- }
- });
- }
- for (let i = 0; i < node.elements.length - 1; i++) {
- const current = node.elements[i];
- const next = node.elements[i + 1];
- if (!current || !next) continue;
- if (current.loc.end.line !== next.loc.start.line - 1) {
- context.report({
- node: next,
- message: "Each array element should be exactly on the next line (no empty lines)",
- fix(fixer) {
- const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
- const baseIndent = openBracketLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- const comma = sourceCode.getTokenBefore(next);
- return fixer.replaceTextRange(
- [
- comma.range[1],
- next.range[0]
- ],
- "\n" + indent
- );
- }
- });
- }
- }
- const lastElement = node.elements[node.elements.length - 1];
- if (lastElement && lastElement.loc.end.line !== closingBracket.loc.start.line - 1) {
- context.report({
- node: closingBracket,
- message: "Array closing bracket should be exactly on the next line",
- fix(fixer) {
- const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
- const baseIndent = openBracketLine.match(/^\s*/)[0];
- const tokenAfter = sourceCode.getTokenAfter(lastElement);
- const hasTrailingComma = tokenAfter && tokenAfter.value === ",";
- const startRange = hasTrailingComma ? tokenAfter.range[1] : lastElement.range[1];
- return fixer.replaceTextRange(
- [
- startRange,
- closingBracket.range[0]
- ],
- "\n" + baseIndent
- );
- }
- });
- }
- const tokenAfter = sourceCode.getTokenAfter(lastElement);
- if (tokenAfter && tokenAfter.value === "," && tokenAfter.range[1] <= closingBracket.range[0]) {
- context.report({
- node: lastElement,
- message: "No trailing comma in arrays",
- fix(fixer) {
- return fixer.remove(tokenAfter);
- }
- });
- }
- }
- };
- }
- },
- "custom-import-order": {
- meta: {
- type: "suggestion",
- fixable: "code",
- schema: []
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- const imports = [];
- return {
- ImportDeclaration(node) {
- imports.push(node);
- },
- "Program:exit"() {
- if (imports.length <= 1) return;
- const categorized = categorizeImports(imports, sourceCode);
- for (let i = 0; i < imports.length - 1; i++) {
- const current = imports[i];
- const next = imports[i + 1];
- const currentCategory = getImportCategory(current, sourceCode);
- const nextCategory = getImportCategory(next, sourceCode);
- if (currentCategory > nextCategory) {
- context.report({
- node: next,
- message: `Import from "${next.source.value}" should come before "${current.source.value}"`,
- fix(fixer) {
- return fixImportOrder(fixer, imports, categorized, sourceCode);
- }
- });
- break;
- }
- }
- }
- };
- function getImportCategory(node, sourceCode) {
- const source = node.source.value;
- const code = sourceCode.getText(node);
- if (source === "react") return 1;
- if (source === "react-native") return 2;
- if (source.startsWith("./")) {
- if (code.includes("import type") || source.includes(".types") || source.includes("/type")) {
- return 3.1;
- }
- if (source.includes("style") || source.includes("Style")) {
- return 3.2;
- }
- return 3.3;
- }
- const specifiers = node.specifiers
- .filter(s => s.type === "ImportSpecifier")
- .map(s => s.imported ? s.imported.name : "");
- const hasNCoreHook = specifiers.some(name => name.startsWith("NCore"));
- const hasUseHook = specifiers.some(name => name.startsWith("use"));
- if (hasNCoreHook) return 4.1;
- if (hasUseHook) return 4.2;
- if (code.includes("import type") || source.includes("/types/") || source.includes("/type") || source.includes(".types")) {
- return 5;
- }
- if (source.startsWith("@/") || source.startsWith("~/")) return 6.3;
- if (!source.startsWith(".")) return 6.1;
- if (source.startsWith("../")) return 6.2;
- return 6.4;
- }
- function categorizeImports(imports, sourceCode) {
- return imports.map(imp => ({
- node: imp,
- category: getImportCategory(imp, sourceCode),
- source: imp.source.value
- })).sort((a, b) => {
- if (a.category !== b.category) {
- return a.category - b.category;
- }
- return a.source.localeCompare(b.source);
- });
- }
- function fixImportOrder(fixer, imports, categorized, sourceCode) {
- const firstImport = imports[0];
- const lastImport = imports[imports.length - 1];
- const rangeStart = firstImport.range[0];
- const rangeEnd = lastImport.range[1];
- let newImports = "";
- categorized.forEach((imp, index) => {
- newImports += sourceCode.getText(imp.node);
- if (index < categorized.length - 1) {
- newImports += "\n";
- }
- });
- return fixer.replaceTextRange([
- rangeStart,
- rangeEnd
- ], newImports);
- }
- }
- },
- "multiline-jsx-children": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- return {
- JSXElement(node) {
- if (!node.closingElement) return;
- if (node.children.length === 0) return;
- const meaningfulChildren = node.children.filter(child => {
- if (child.type === "JSXText" && child.value.trim() === "") return false;
- return true;
- });
- if (meaningfulChildren.length === 0) return;
- const openingElement = node.openingElement;
- const closingElement = node.closingElement;
- const isMultilineOpening = openingElement.loc.start.line !== openingElement.loc.end.line;
- if (!isMultilineOpening) return;
- const sourceCode = context.getSourceCode();
- if (meaningfulChildren.length === 1 && meaningfulChildren[0].type === "JSXText") {
- const child = meaningfulChildren[0];
- const text = sourceCode.getText(child);
- const hasLeadingNewline = /^\s*\n/.test(text);
- const hasTrailingNewline = /\n\s*$/.test(text);
- if (!hasLeadingNewline || !hasTrailingNewline) {
- context.report({
- node: child,
- message: "Multiline JSX element text content must be on its own line",
- fix(fixer) {
- const openBracketLine = sourceCode.lines[openingElement.loc.start.line - 1];
- const baseIndent = openBracketLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- return fixer.replaceText(child, "\n" + indent + text.trim() + "\n" + baseIndent);
- }
- });
- }
- return;
- }
- const firstChild = meaningfulChildren[0];
- const openingEndLine = openingElement.loc.end.line;
- let needsFirstChildFix = false;
- if (firstChild.type === "JSXText") {
- const text = sourceCode.getText(firstChild);
- if (!/^\s*\n/.test(text)) needsFirstChildFix = true;
- } else if (firstChild.loc.start.line === openingEndLine) {
- needsFirstChildFix = true;
- }
- if (needsFirstChildFix) {
- context.report({
- node: firstChild,
- message: "First child of a multiline JSX element must be on a new line",
- fix(fixer) {
- const openBracketLine = sourceCode.lines[openingElement.loc.start.line - 1];
- const baseIndent = openBracketLine.match(/^\s*/)[0];
- const indent = baseIndent + " ";
- if (firstChild.type === "JSXText") {
- const text = sourceCode.getText(firstChild);
- return fixer.replaceText(firstChild, "\n" + indent + text.replace(/^\s+/, ""));
- }
- return fixer.insertTextBefore(firstChild, "\n" + indent);
- }
- });
- }
- const lastChild = meaningfulChildren[meaningfulChildren.length - 1];
- let needsLastChildFix = false;
- if (lastChild.type === "JSXText") {
- const text = sourceCode.getText(lastChild);
- if (!/\n\s*$/.test(text)) needsLastChildFix = true;
- } else if (lastChild.loc.end.line === closingElement.loc.start.line) {
- needsLastChildFix = true;
- }
- if (needsLastChildFix) {
- context.report({
- node: closingElement,
- message: "Closing tag of a multiline JSX element must be on a new line",
- fix(fixer) {
- const openBracketLine = sourceCode.lines[openingElement.loc.start.line - 1];
- const baseIndent = openBracketLine.match(/^\s*/)[0];
- if (lastChild.type === "JSXText") {
- const text = sourceCode.getText(lastChild);
- return fixer.replaceText(lastChild, text.replace(/\s+$/, "") + "\n" + baseIndent);
- }
- return fixer.insertTextBefore(closingElement, "\n" + baseIndent);
- }
- });
- }
- }
- };
- }
- },
- "no-jsx-parens-in-return": {
- meta: {
- type: "layout",
- fixable: "code",
- schema: []
- },
- create(context) {
- return {
- ReturnStatement(node) {
- if (!node.argument) return;
- if (node.argument.type === "JSXElement" || node.argument.type === "JSXFragment") {
- const sourceCode = context.getSourceCode();
- const returnToken = sourceCode.getFirstToken(node);
- const firstChildToken = sourceCode.getFirstToken(node.argument);
- const tokensBetween = sourceCode.getTokensBetween(returnToken, firstChildToken);
- const openParen = tokensBetween.find(t => t.value === "(");
- if (openParen) {
- const closeParen = sourceCode.getTokenAfter(node.argument);
- if (closeParen && closeParen.value === ")") {
- context.report({
- node: node.argument,
- message: "Returned JSX element should not be wrapped in parentheses",
- fix(fixer) {
- const fixes = [
- fixer.remove(closeParen)
- ];
- if (returnToken.loc.end.line !== firstChildToken.loc.start.line) {
- fixes.push(fixer.replaceTextRange([
- returnToken.range[1],
- firstChildToken.range[0]
- ], " "));
- } else {
- fixes.push(fixer.remove(openParen));
- }
- return fixes;
- }
- });
- }
- } else if (returnToken.loc.end.line !== firstChildToken.loc.start.line) {
- context.report({
- node: node.argument,
- message: "Returned JSX element must start on the same line as 'return' to avoid Automatic Semicolon Insertion (ASI)",
- fix(fixer) {
- return fixer.replaceTextRange([
- returnToken.range[1],
- firstChildToken.range[0]
- ], " ");
- }
- });
- }
- }
- },
- ArrowFunctionExpression(node) {
- if (node.body.type === "JSXElement" || node.body.type === "JSXFragment") {
- const sourceCode = context.getSourceCode();
- const arrowToken = sourceCode.getTokenBefore(node.body, t => t.value === "=>");
- const firstChildToken = sourceCode.getFirstToken(node.body);
- const tokensBetween = sourceCode.getTokensBetween(arrowToken, firstChildToken);
- const openParen = tokensBetween.find(t => t.value === "(");
- if (openParen) {
- const closeParen = sourceCode.getTokenAfter(node.body);
- if (closeParen && closeParen.value === ")") {
- context.report({
- node: node.body,
- message: "Implicitly returned JSX element should not be wrapped in parentheses",
- fix(fixer) {
- const fixes = [
- fixer.remove(closeParen)
- ];
- if (arrowToken.loc.end.line !== firstChildToken.loc.start.line) {
- fixes.push(fixer.replaceTextRange([
- arrowToken.range[1],
- firstChildToken.range[0]
- ], " "));
- } else {
- fixes.push(fixer.remove(openParen));
- }
- return fixes;
- }
- });
- }
- } else if (arrowToken.loc.end.line !== firstChildToken.loc.start.line) {
- context.report({
- node: node.body,
- message: "Implicitly returned JSX element must start on the same line as '=>'",
- fix(fixer) {
- return fixer.replaceTextRange([
- arrowToken.range[1],
- firstChildToken.range[0]
- ], " ");
- }
- });
- }
- }
- }
- };
- }
- },
- "no-newline-after-jsx": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- function checkPunctuation(node) {
- const sourceCode = context.getSourceCode();
- const lastToken = sourceCode.getLastToken(node);
- const nextToken = sourceCode.getTokenAfter(node);
- if (nextToken && (nextToken.value === ";" || nextToken.value === "," || nextToken.value === ")")) {
- if (lastToken.loc.end.line !== nextToken.loc.start.line) {
- if (sourceCode.commentsExistBetween(node, nextToken)) return;
- context.report({
- node: nextToken,
- message: `Punctuation '${nextToken.value}' should be on the same line as the preceding JSX element`,
- fix(fixer) {
- return fixer.replaceTextRange([
- node.range[1],
- nextToken.range[0]
- ], "");
- }
- });
- }
- }
- }
- return {
- JSXElement: checkPunctuation,
- JSXFragment: checkPunctuation
- };
- }
- },
- "no-static-inline-styles": {
- meta: {
- type: "suggestion",
- messages: {
- staticStyle: "Static inline styles are not allowed. Please move them to a stylesheet file."
- },
- schema: []
- },
- create(context) {
- function checkProperty(propertyNode) {
- if (!propertyNode || propertyNode.type !== "Property") return;
- const value = propertyNode.value;
- if (!value) return;
- let isStatic = false;
- if (value.type === "Literal") {
- if (typeof value.value === "string" || typeof value.value === "number") {
- isStatic = true;
- }
- } else if (value.type === "UnaryExpression" && value.operator === "-") {
- if (value.argument && value.argument.type === "Literal" && typeof value.argument.value === "number") {
- isStatic = true;
- }
- } else if (value.type === "TemplateLiteral" && value.expressions.length === 0) {
- isStatic = true;
- }
- if (isStatic) {
- context.report({
- node: propertyNode,
- messageId: "staticStyle"
- });
- }
- }
- function checkObjectExpression(node) {
- if (node.type !== "ObjectExpression") return;
- node.properties.forEach(checkProperty);
- }
- function checkStyleValue(node) {
- if (!node) return;
- if (node.type === "ObjectExpression") {
- checkObjectExpression(node);
- } else if (node.type === "ArrayExpression") {
- node.elements.forEach(element => {
- if (element && element.type === "ObjectExpression") {
- checkObjectExpression(element);
- }
- });
- }
- }
- return {
- JSXAttribute(node) {
- const attrName = node.name.name;
- if (typeof attrName !== "string") return;
- if (attrName === "style" || attrName.endsWith("Style")) {
- if (node.value && node.value.type === "JSXExpressionContainer") {
- checkStyleValue(node.value.expression);
- }
- }
- }
- };
- }
- },
- "jsx-ternary-formatting": {
- meta: {
- type: "layout",
- fixable: "whitespace",
- schema: []
- },
- create(context) {
- return {
- JSXExpressionContainer(node) {
- if (node.expression.type !== "ConditionalExpression") return;
- const sourceCode = context.getSourceCode();
- const openBrace = sourceCode.getFirstToken(node);
- const closeBrace = sourceCode.getLastToken(node);
- const cond = node.expression;
- let hasJSX = false;
- if (
- cond.consequent.type === "JSXElement" ||
- cond.consequent.type === "JSXFragment" ||
- cond.alternate.type === "JSXElement" ||
- cond.alternate.type === "JSXFragment"
- ) {
- hasJSX = true;
- }
- if (!hasJSX) return;
- const checkAndRemoveParens = (nodeToUnparen) => {
- const currentToken = sourceCode.getTokenBefore(nodeToUnparen);
- const afterToken = sourceCode.getTokenAfter(nodeToUnparen);
- let removed = false;
- while (currentToken && currentToken.value === "(" && afterToken && afterToken.value === ")") {
- context.report({
- node: currentToken,
- message: "Parentheses are not allowed around JSX in ternaries.",
- fix(fixer) {
- return [
- fixer.remove(currentToken),
- fixer.remove(afterToken)
- ];
- }
- });
- removed = true;
- break;
- }
- return removed;
- };
- let hasParens = false;
- if (cond.consequent.type === "JSXElement" || cond.consequent.type === "JSXFragment") {
- if (checkAndRemoveParens(cond.consequent)) hasParens = true;
- }
- if (cond.alternate.type === "JSXElement" || cond.alternate.type === "JSXFragment") {
- if (checkAndRemoveParens(cond.alternate)) hasParens = true;
- }
- if (hasParens) return; // Wait for the next fix pass to format the whitespace properly
- const questionToken = sourceCode.getTokenAfter(cond.test, {
- filter: t => t.value === "?"
- });
- const colonToken = sourceCode.getTokenAfter(cond.consequent, {
- filter: t => t.value === ":"
- });
- const getIndent = (token) => {
- const line = sourceCode.lines[token.loc.start.line - 1];
- return line.match(/^\s*/)[0];
- };
- const openIndent = getIndent(openBrace);
- const baseIndent = openIndent + " ";
- const innerIndent = baseIndent + " ";
- const checkAndFix = (token1, token2, expectedWhitespace) => {
- let textBetween = sourceCode.getText().slice(token1.range[1], token2.range[0]);
- textBetween = textBetween.replace(/\r\n/g, "\n");
- if (textBetween !== expectedWhitespace) {
- context.report({
- node: token2,
- message: "Incorrect ternary formatting",
- fix(fixer) {
- return fixer.replaceTextRange([
- token1.range[1],
- token2.range[0]
- ], expectedWhitespace);
- }
- });
- }
- };
- const firstTestToken = sourceCode.getTokenAfter(openBrace);
- checkAndFix(openBrace, firstTestToken, "\n" + baseIndent);
- const firstConsequentToken = sourceCode.getTokenAfter(questionToken);
- checkAndFix(questionToken, firstConsequentToken, "\n" + innerIndent);
- const firstColonToken = colonToken;
- const lastConsequentToken = sourceCode.getTokenBefore(colonToken);
- checkAndFix(lastConsequentToken, firstColonToken, "\n" + baseIndent);
- const firstAlternateToken = sourceCode.getTokenAfter(colonToken);
- checkAndFix(colonToken, firstAlternateToken, "\n" + innerIndent);
- const lastAlternateToken = sourceCode.getTokenBefore(closeBrace);
- checkAndFix(lastAlternateToken, closeBrace, "\n" + openIndent);
- }
- };
- }
- }
- };
|