| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956 |
- function isStyleObject(node) {
- if (!node || !node.properties) return false;
- if (node.type !== "ObjectExpression") return false;
- if (node.properties.length <= 1) return false;
- if (node.parent && node.parent.type === "CallExpression" &&
- node.parent.callee.type === "MemberExpression" &&
- node.parent.callee.object.name === "StyleSheet" &&
- node.parent.callee.property.name === "create") {
- return true;
- }
- if (node.parent && node.parent.type === "Property" &&
- node.parent.parent && node.parent.parent.type === "ObjectExpression" &&
- node.parent.parent.parent && node.parent.parent.parent.type === "CallExpression" &&
- node.parent.parent.parent.callee.type === "MemberExpression" &&
- node.parent.parent.parent.callee.object.name === "StyleSheet") {
- return true;
- }
- const knownStyleKeys = new Set([
- "alignItems",
- "justifyContent",
- "flexDirection",
- "flexWrap",
- "flex",
- "padding",
- "margin",
- "paddingTop",
- "marginTop",
- "paddingBottom",
- "marginBottom",
- "paddingLeft",
- "marginLeft",
- "paddingRight",
- "marginRight",
- "paddingHorizontal",
- "marginHorizontal",
- "paddingVertical",
- "marginVertical",
- "backgroundColor",
- "color",
- "fontSize",
- "fontWeight",
- "lineHeight",
- "textAlign",
- "borderWidth",
- "borderRadius",
- "borderColor",
- "borderStyle",
- "position",
- "top",
- "bottom",
- "left",
- "right",
- "display",
- "width",
- "height",
- "minWidth",
- "minHeight",
- "maxWidth",
- "maxHeight",
- "alignSelf"
- ]);
- let hasStyleKey = false;
- for (const prop of node.properties) {
- if (prop.type === "Property" && prop.key && prop.key.type === "Identifier") {
- if (knownStyleKeys.has(prop.key.name)) {
- hasStyleKey = true;
- break;
- }
- }
- }
- if (hasStyleKey) return true;
- if (node.parent && node.parent.type === "VariableDeclarator" && node.parent.id &&
- (node.parent.id.name === "styles" || node.parent.id.name === "stylesheet" || node.parent.id.name === "style")) {
- return true;
- }
- if (node.parent && (node.parent.type === "TSAsExpression" || node.parent.type === "TypeAssertion")) {
- const typeName = node.parent.typeAnnotation?.typeName?.name;
- if (typeName && (typeName === "ViewStyle" || typeName === "TextStyle" || typeName === "ImageStyle")) {
- return true;
- }
- }
- if (node.parent && node.parent.type === "Property" && node.parent.parent && node.parent.parent.type === "ObjectExpression") {
- if (node.parent.parent.parent && node.parent.parent.parent.type === "VariableDeclarator" && node.parent.parent.parent.id &&
- (node.parent.parent.parent.id.name === "styles" || node.parent.parent.parent.id.name === "stylesheet")) {
- return true;
- }
- }
- return false;
- }
- module.exports = {
- "ncore-sort-styles": {
- create(context) {
- const sourceCode = context.getSourceCode();
- const shorthandDependencies = {
- borderRadius: [
- "borderTopLeftRadius",
- "borderTopRightRadius",
- "borderBottomLeftRadius",
- "borderBottomRightRadius",
- "borderTopStartRadius",
- "borderTopEndRadius",
- "borderBottomStartRadius",
- "borderBottomEndRadius"
- ],
- padding: [
- "paddingHorizontal",
- "paddingVertical",
- "paddingTop",
- "paddingRight",
- "paddingBottom",
- "paddingLeft",
- "paddingStart",
- "paddingEnd"
- ],
- borderColor: [
- "borderTopColor",
- "borderRightColor",
- "borderBottomColor",
- "borderLeftColor",
- "borderStartColor",
- "borderEndColor"
- ],
- borderWidth: [
- "borderTopWidth",
- "borderRightWidth",
- "borderBottomWidth",
- "borderLeftWidth",
- "borderStartWidth",
- "borderEndWidth"
- ],
- borderStyle: [
- "borderTopStyle",
- "borderRightStyle",
- "borderBottomStyle",
- "borderLeftStyle"
- ],
- margin: [
- "marginHorizontal",
- "marginVertical",
- "marginTop",
- "marginRight",
- "marginBottom",
- "marginLeft",
- "marginStart",
- "marginEnd"
- ],
- paddingHorizontal: [
- "paddingLeft",
- "paddingRight",
- "paddingStart",
- "paddingEnd"
- ],
- marginHorizontal: [
- "marginLeft",
- "marginRight",
- "marginStart",
- "marginEnd"
- ],
- paddingVertical: [
- "paddingTop",
- "paddingBottom"
- ],
- marginVertical: [
- "marginTop",
- "marginBottom"
- ],
- flex: [
- "flexGrow",
- "flexShrink",
- "flexBasis"
- ]
- };
- function getSortedProperties(properties) {
- const webStyles = [];
- const others = [];
- for (const p of properties) {
- if (p.type === "SpreadElement" && p.argument.type === "CallExpression" && p.argument.callee.name === "webStyle") {
- webStyles.push(p);
- } else {
- others.push(p);
- }
- }
- const chunks = [];
- let currentChunk = [];
- const chunkSeparators = [];
- for (const p of others) {
- if (p.type === "SpreadElement") {
- chunks.push(currentChunk);
- chunkSeparators.push(p);
- currentChunk = [];
- } else {
- currentChunk.push(p);
- }
- }
- chunks.push(currentChunk);
- function sortChunk(chunk) {
- if (chunk.length <= 1) return chunk;
- const getLen = (node) => {
- let maxLen = 0;
- for (let i = node.loc.start.line - 1; i < node.loc.end.line; i++) {
- const lineLen = sourceCode.lines[i].trimRight().length;
- if (lineLen > maxLen) {
- maxLen = lineLen;
- }
- }
- return maxLen;
- };
- const initialSorted = chunk.map((node, i) => ({
- length: getLen(node),
- originalIndex: i,
- node
- })).sort((a, b) => {
- if (a.length !== b.length) return b.length - a.length;
- return a.originalIndex - b.originalIndex;
- });
- const propNames = chunk.map(p => {
- if (p.type === "Property") {
- if (p.key.type === "Identifier") return p.key.name;
- if (p.key.type === "Literal") return p.key.value;
- }
- return null;
- });
- const prerequisites = new Map();
- for (let i = 0; i < chunk.length; i++) {
- prerequisites.set(i, new Set());
- }
- for (let i = 0; i < propNames.length; i++) {
- const nameA = propNames[i];
- if (!nameA) continue;
- const deps = shorthandDependencies[nameA];
- if (deps) {
- for (let j = 0; j < propNames.length; j++) {
- if (i === j) continue;
- if (deps.includes(propNames[j])) {
- prerequisites.get(j).add(i);
- }
- }
- }
- }
- const result = [];
- const placedIndices = new Set();
- const pool = [...initialSorted];
- while (pool.length > 0) {
- let placedAny = false;
- for (let i = 0; i < pool.length; i++) {
- const item = pool[i];
- const itemOriginalIndex = item.originalIndex;
- const reqs = prerequisites.get(itemOriginalIndex);
- let canPlace = true;
- for (const req of reqs) {
- if (!placedIndices.has(req)) {
- canPlace = false;
- break;
- }
- }
- if (canPlace) {
- result.push(item.node);
- placedIndices.add(itemOriginalIndex);
- pool.splice(i, 1);
- placedAny = true;
- break;
- }
- }
- if (!placedAny) {
- const item = pool.shift();
- result.push(item.node);
- placedIndices.add(item.originalIndex);
- }
- }
- return result;
- }
- const finalResult = [];
- for (let i = 0; i < chunks.length; i++) {
- finalResult.push(...sortChunk(chunks[i]));
- if (i < chunkSeparators.length) {
- finalResult.push(chunkSeparators[i]);
- }
- }
- finalResult.push(...webStyles);
- return finalResult;
- }
- return {
- ObjectExpression(node) {
- if (!isStyleObject(node)) return;
- const oldOrder = node.properties;
- if (oldOrder.length <= 1) return;
- const newOrder = getSortedProperties(oldOrder);
- let changed = false;
- for (let i = 0; i < oldOrder.length; i++) {
- if (oldOrder[i] !== newOrder[i]) {
- changed = true;
- break;
- }
- }
- if (changed) {
- context.report({
- message: "Style properties should be sorted by length (longest to shortest), with shorthand properties appropriately ordered and spread elements at the bottom.",
- fix(fixer) {
- const fixes = [];
- for (let i = 0; i < oldOrder.length; i++) {
- fixes.push(fixer.replaceText(oldOrder[i], sourceCode.getText(newOrder[i])));
- }
- return fixes;
- },
- node: node
- });
- }
- }
- };
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "no-jsx-parens-in-return": {
- 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({
- 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;
- },
- message: "Returned JSX element should not be wrapped in parentheses",
- node: node.argument
- });
- }
- } else if (returnToken.loc.end.line !== firstChildToken.loc.start.line) {
- context.report({
- 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]
- ], " ");
- },
- node: node.argument
- });
- }
- }
- },
- 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({
- 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;
- },
- node: node.body
- });
- }
- } else if (arrowToken.loc.end.line !== firstChildToken.loc.start.line) {
- context.report({
- message: "Implicitly returned JSX element must start on the same line as '=>'",
- fix(fixer) {
- return fixer.replaceTextRange([
- arrowToken.range[1],
- firstChildToken.range[0]
- ], " ");
- },
- node: node.body
- });
- }
- }
- }
- };
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "ncore-format-union-types": {
- create(context) {
- const sourceCode = context.getSourceCode();
- return {
- TSUnionType(node) {
- if (node.types.length < 2) return;
- if (node.loc.start.line === node.loc.end.line) return;
- let parentStmt = node.parent;
- while (parentStmt && parentStmt.type !== "ExportNamedDeclaration" && parentStmt.type !== "TSTypeAliasDeclaration") {
- parentStmt = parentStmt.parent;
- }
- const baseIndentLine = parentStmt ? sourceCode.lines[parentStmt.loc.start.line - 1] : "";
- const baseIndent = baseIndentLine ? baseIndentLine.match(/^\s*/)[0] : "";
- const indent = baseIndent + " ";
- let hasError = false;
- const beforeToken = sourceCode.getTokenBefore(node);
- const firstToken = sourceCode.getFirstToken(node);
- let leadingPipeToken = null;
- if (firstToken && firstToken.value === "|") {
- leadingPipeToken = firstToken;
- } else if (beforeToken && beforeToken.value === "|") {
- leadingPipeToken = beforeToken;
- }
- const hasLeadingPipe = leadingPipeToken !== null;
- if (hasLeadingPipe) {
- const prevToken = sourceCode.getTokenBefore(leadingPipeToken);
- if (prevToken && prevToken.loc.end.line !== leadingPipeToken.loc.start.line) {
- hasError = true;
- }
- const tokenAfterLeadingPipe = sourceCode.getTokenAfter(leadingPipeToken);
- if (tokenAfterLeadingPipe && leadingPipeToken.loc.end.line === tokenAfterLeadingPipe.loc.start.line) {
- hasError = true;
- }
- } else {
- hasError = true;
- }
- for (let i = 0; i < node.types.length; i++) {
- const currentType = node.types[i];
- if (i < node.types.length - 1) {
- const pipeToken = sourceCode.getTokenAfter(currentType, token => token.value === "|");
- if (pipeToken) {
- if (pipeToken.loc.start.line !== currentType.loc.end.line) {
- hasError = true;
- }
- const nextType = node.types[i + 1];
- if (pipeToken.loc.end.line === nextType.loc.start.line) {
- hasError = true;
- }
- } else {
- hasError = true;
- }
- }
- }
- if (hasError) {
- context.report({
- fix(fixer) {
- let expectedText = " |\n";
- for (let i = 0; i < node.types.length; i++) {
- const typeText = sourceCode.getText(node.types[i]);
- let formattedText = typeText;
- if (node.types[i].type === "TSTypeLiteral" && node.types[i].loc.start.line === node.types[i].loc.end.line) {
- const membersText = node.types[i].members.map(m => indent + " " + sourceCode.getText(m)).join("\n");
- formattedText = "{\n" + membersText + "\n" + indent + "}";
- }
- expectedText += indent + formattedText;
- if (i < node.types.length - 1) {
- expectedText += " |\n";
- }
- }
- const replaceStart = leadingPipeToken ? leadingPipeToken.range[0] : node.range[0];
- const prevToken = sourceCode.getTokenBefore(leadingPipeToken || node);
- const actualStart = prevToken ? prevToken.range[1] : replaceStart;
- return fixer.replaceTextRange([
- actualStart,
- node.range[1]
- ], expectedText);
- },
- message: "Multiline union types should have a leading pipe and trailing pipes at the end of each line.",
- node
- });
- }
- }
- };
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "custom-import-order": {
- create(context) {
- const sourceCode = context.getSourceCode();
- const imports = [];
- return {
- "Program:exit"() {
- if (imports.length <= 1) return;
- const categorized = categorizeImports(imports, sourceCode);
- let isSorted = true;
- let errorNode = null;
- for (let i = 0; i < imports.length; i++) {
- if (imports[i] !== categorized[i].node) {
- isSorted = false;
- errorNode = imports[i];
- break;
- }
- }
- if (!isSorted) {
- context.report({
- message: "Imports are not sorted correctly according to custom rules.",
- fix(fixer) {
- return fixImportOrder(fixer, imports, categorized, sourceCode);
- },
- node: errorNode
- });
- }
- },
- ImportDeclaration(node) {
- imports.push(node);
- }
- };
- 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 => {
- const isDefaultImport = imp.specifiers.some(s => s.type === "ImportDefaultSpecifier");
- const textLength = sourceCode.getText(imp).replace(/\s+/g, "").length;
- return {
- category: getImportCategory(imp, sourceCode),
- source: imp.source.value,
- isDefaultImport,
- textLength,
- node: imp
- };
- }).sort((a, b) => {
- if (a.category !== b.category) {
- return a.category - b.category;
- }
- if (a.category >= 6) {
- if (a.isDefaultImport !== b.isDefaultImport) {
- return a.isDefaultImport ? 1 : -1;
- }
- if (a.textLength !== b.textLength) {
- return b.textLength - a.textLength;
- }
- }
- 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);
- }
- },
- meta: {
- type: "suggestion",
- fixable: "code",
- schema: []
- }
- },
- "sort-objects-by-length": {
- create(context) {
- const sourceCode = context.getSourceCode();
- function sortGroup(group) {
- const getLen = (n) => {
- let maxLen = 0;
- for (let i = n.loc.start.line - 1; i < n.loc.end.line; i++) {
- const lineLen = sourceCode.lines[i].trimRight().length;
- if (lineLen > maxLen) {
- maxLen = lineLen;
- }
- }
- return maxLen;
- };
- const sortedGroup = [...group].sort((a, b) => {
- const lenA = getLen(a);
- const lenB = getLen(b);
- if (lenA !== lenB) {
- return lenB - lenA;
- }
- return sourceCode.getText(a).localeCompare(sourceCode.getText(b));
- });
- let isSorted = true;
- for (let i = 0; i < group.length; i++) {
- if (group[i] !== sortedGroup[i]) {
- isSorted = false;
- break;
- }
- }
- if (!isSorted) {
- context.report({
- message: "Object properties must be sorted from longest to shortest string length.",
- fix(fixer) {
- const fixes = [];
- const texts = sortedGroup.map(n => sourceCode.getText(n));
- for (let i = 0; i < group.length; i++) {
- fixes.push(fixer.replaceText(group[i], texts[i]));
- }
- return fixes;
- },
- node: group[0]
- });
- }
- }
- function checkProperties(node) {
- if (isStyleObject(node)) return;
- let properties = node.properties || node.members;
- if (!properties && node.body && Array.isArray(node.body)) {
- properties = node.body;
- }
- if (!properties || !Array.isArray(properties) || properties.length < 2) return;
- const groups = [];
- let currentGroup = [];
- for (let i = 0; i < properties.length; i++) {
- const prop = properties[i];
- if (prop && (prop.type === "Property" || prop.type === "TSPropertySignature" || prop.type === "TSMethodSignature")) {
- currentGroup.push(prop);
- } else {
- if (currentGroup.length > 1) groups.push(currentGroup);
- currentGroup = [];
- }
- }
- if (currentGroup.length > 1) groups.push(currentGroup);
- groups.forEach(sortGroup);
- }
- return {
- ObjectExpression: checkProperties,
- ObjectPattern: checkProperties
- };
- },
- meta: {
- type: "suggestion",
- fixable: "code",
- schema: []
- }
- },
- "no-newline-after-jsx": {
- 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({
- 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]
- ], "");
- },
- node: nextToken
- });
- }
- }
- }
- return {
- JSXFragment: checkPunctuation,
- JSXElement: checkPunctuation
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "multiline-import-specifiers": {
- create(context) {
- return {
- ImportDeclaration(node) {
- const specifiers = node.specifiers.filter(
- s => s.type === "ImportSpecifier"
- );
- if (specifiers.length === 0) return;
- const sourceCode = context.getSourceCode();
- const openBrace = sourceCode.getFirstToken(node, t => t.value === "{");
- const closeBrace = sourceCode.getLastToken(node, t => t.value === "}");
- if (!openBrace || !closeBrace) return;
- const firstSpecifier = specifiers[0];
- if (firstSpecifier.loc.start.line - openBrace.loc.end.line !== 1) {
- context.report({
- message: "There should be exactly one newline after '{'.",
- fix(fixer) {
- return fixer.replaceTextRange([
- openBrace.range[1],
- firstSpecifier.range[0]
- ], "\n ");
- },
- node: firstSpecifier
- });
- }
- for (let i = 0; i < specifiers.length - 1; i++) {
- const current = specifiers[i];
- const next = specifiers[i + 1];
- if (next.loc.start.line - current.loc.end.line !== 1) {
- context.report({
- message: "Each import specifier should be on a new line without blank lines between them.",
- fix(fixer) {
- const comma = sourceCode.getTokenBefore(next);
- return fixer.replaceTextRange(
- [
- comma.range[1],
- next.range[0]
- ],
- "\n "
- );
- },
- node: next
- });
- }
- }
- const lastSpecifier = specifiers[specifiers.length - 1];
- const tokenAfter = sourceCode.getTokenAfter(lastSpecifier);
- if (tokenAfter && tokenAfter.value === ",") {
- context.report({
- message: "No trailing comma in imports",
- fix(fixer) {
- return fixer.remove(tokenAfter);
- },
- node: lastSpecifier
- });
- }
- const tokenBeforeClose = sourceCode.getTokenBefore(closeBrace);
- if (closeBrace.loc.start.line - tokenBeforeClose.loc.end.line !== 1) {
- context.report({
- message: "There should be exactly one newline before '}'.",
- fix(fixer) {
- return fixer.replaceTextRange([
- tokenBeforeClose.range[1],
- closeBrace.range[0]
- ], "\n");
- },
- node: closeBrace
- });
- }
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "no-static-inline-styles": {
- 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({
- messageId: "staticStyle",
- node: propertyNode
- });
- }
- }
- 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 {
- VariableDeclarator(node) {
- if (node.id && node.id.name === "styles" && node.init && node.init.type === "ObjectExpression") {
- let parent = node.parent;
- let isInsideUseStyles = false;
- while (parent) {
- if (parent.type === "VariableDeclarator" && parent.id && parent.id.name === "useStyles") {
- isInsideUseStyles = true;
- break;
- }
- if (parent.type === "FunctionDeclaration" && parent.id && parent.id.name === "useStyles") {
- isInsideUseStyles = true;
- break;
- }
- parent = parent.parent;
- }
- if (isInsideUseStyles) {
- node.init.properties.forEach(styleClass => {
- let styleObj = styleClass.value;
- if (styleObj && styleObj.type === "TSAsExpression") {
- styleObj = styleObj.expression;
- }
- if (styleObj && styleObj.type === "ObjectExpression") {
- styleObj.properties.forEach(prop => {
- checkProperty(prop);
- });
- }
- });
- }
- }
- },
- 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);
- }
- }
- }
- };
- },
- meta: {
- messages: {
- staticStyle: "Static inline styles are not allowed. Please move them to a stylesheet file."
- },
- type: "suggestion",
- schema: []
- }
- },
- "sort-jsx-props": {
- create(context) {
- return {
- JSXOpeningElement(node) {
- const attributes = node.attributes;
- if (!attributes || attributes.length <= 1) return;
- const sourceCode = context.getSourceCode();
- const getAttrLength = (attr) => {
- let maxLen = 0;
- for (let i = attr.loc.start.line - 1; i < attr.loc.end.line; i++) {
- const lineLen = sourceCode.lines[i].trimRight().length;
- if (lineLen > maxLen) {
- maxLen = lineLen;
- }
- }
- return maxLen;
- };
- const groups = [];
- let currentGroup = [];
- for (const attr of attributes) {
- if (attr.type === "JSXSpreadAttribute") {
- if (currentGroup.length > 1) {
- groups.push(currentGroup);
- }
- currentGroup = [];
- } else {
- currentGroup.push(attr);
- }
- }
- if (currentGroup.length > 1) {
- groups.push(currentGroup);
- }
- for (const group of groups) {
- const sortedGroup = [...group].sort((a, b) => {
- return getAttrLength(b) - getAttrLength(a);
- });
- let isSorted = true;
- for (let i = 0; i < group.length; i++) {
- if (group[i] !== sortedGroup[i]) {
- isSorted = false;
- break;
- }
- }
- if (!isSorted) {
- context.report({
- fix(fixer) {
- const firstAttr = group[0];
- const startLoc = firstAttr.loc.start;
- const indent = " ".repeat(startLoc.column);
- const newText = sortedGroup.map(attr => sourceCode.getText(attr)).join("\n" + indent);
- const start = group[0].range[0];
- const end = group[group.length - 1].range[1];
- return fixer.replaceTextRange([
- start,
- end
- ], newText);
- },
- message: "JSX attributes should be sorted from longest to shortest.",
- node: group[0]
- });
- }
- }
- }
- };
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "sort-translations": {
- create(context) {
- const sourceCode = context.getSourceCode();
- function checkObject(node) {
- if (!node.properties || node.properties.length <= 1) return;
- let hasIsRTL = false;
- let hasLocale = false;
- let translationsProp = null;
- for (const prop of node.properties) {
- const keyNode = prop.key;
- if (!keyNode) continue;
- let keyName = "";
- if (keyNode.type === "Identifier") keyName = keyNode.name;
- else if (keyNode.type === "Literal" || keyNode.type === "JSONLiteral") keyName = keyNode.value;
- if (keyName === "isRTL") hasIsRTL = true;
- if (keyName === "locale") hasLocale = true;
- if (keyName === "translations") translationsProp = prop;
- }
- if (hasIsRTL && hasLocale && translationsProp) {
- const translationsObj = translationsProp.value;
- if (!translationsObj || !translationsObj.properties || translationsObj.properties.length <= 1) return;
- const props = translationsObj.properties;
- const sortedProps = [...props].sort((a, b) => {
- const lenA = sourceCode.getText(a.key).length + sourceCode.getText(a.value).length;
- const lenB = sourceCode.getText(b.key).length + sourceCode.getText(b.value).length;
- if (lenA !== lenB) {
- return lenB - lenA;
- }
- const keyA = a.key.value || a.key.name || "";
- const keyB = b.key.value || b.key.name || "";
- return keyA.localeCompare(keyB);
- });
- let isSorted = true;
- let errorNode = null;
- for (let i = 0; i < props.length; i++) {
- if (props[i] !== sortedProps[i]) {
- isSorted = false;
- errorNode = props[i];
- break;
- }
- }
- if (!isSorted) {
- context.report({
- message: "Translations keys must be sorted by length (longest to shortest).",
- fix(fixer) {
- const firstProp = props[0];
- const lastProp = props[props.length - 1];
- const startLoc = firstProp.loc.start;
- const indent = " ".repeat(startLoc.column);
- let newText = "";
- sortedProps.forEach((p, index) => {
- newText += sourceCode.getText(p);
- if (index < sortedProps.length - 1) {
- newText += ",\n" + indent;
- }
- });
- return fixer.replaceTextRange([
- firstProp.range[0],
- lastProp.range[1]
- ], newText);
- },
- node: errorNode
- });
- }
- }
- }
- return {
- JSONObjectExpression: checkObject,
- ObjectExpression: checkObject
- };
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "sort-import-specifiers": {
- create(context) {
- return {
- ExportNamedDeclaration(node) {
- checkSpecifiers(node, "ExportSpecifier", "Export");
- },
- ImportDeclaration(node) {
- checkSpecifiers(node, "ImportSpecifier", "Import");
- }
- };
- function checkSpecifiers(node, specifierType, typeName) {
- if (!node.specifiers) return;
- const specifiers = node.specifiers.filter(
- s => s.type === specifierType
- );
- if (specifiers.length <= 1) return;
- const sourceCode = context.getSourceCode();
- const getSpecLength = (spec) => {
- return sourceCode.getText(spec).replace(/\s+/g, "").length;
- };
- const sortedSpecifiers = [...specifiers].sort((a, b) => {
- return getSpecLength(b) - getSpecLength(a);
- });
- let isSorted = true;
- for (let i = 0; i < specifiers.length; i++) {
- if (specifiers[i] !== sortedSpecifiers[i]) {
- isSorted = false;
- break;
- }
- }
- if (!isSorted) {
- context.report({
- fix(fixer) {
- const firstSpec = specifiers[0];
- const lastSpec = specifiers[specifiers.length - 1];
- const startLoc = firstSpec.loc.start;
- let indent = " ";
- if (firstSpec.loc.start.line !== node.loc.start.line) {
- indent = " ".repeat(startLoc.column);
- }
- const newText = sortedSpecifiers.map(spec => sourceCode.getText(spec)).join(",\n" + indent);
- return fixer.replaceTextRange([
- firstSpec.range[0],
- lastSpec.range[1]
- ], newText);
- },
- message: `${typeName} specifiers should be sorted from longest to shortest.`,
- node
- });
- }
- }
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "jsx-ternary-formatting": {
- 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({
- message: "Parentheses are not allowed around JSX in ternaries.",
- fix(fixer) {
- return [
- fixer.remove(currentToken),
- fixer.remove(afterToken)
- ];
- },
- node: currentToken
- });
- 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({
- message: "Incorrect ternary formatting",
- fix(fixer) {
- return fixer.replaceTextRange([
- token1.range[1],
- token2.range[0]
- ], expectedWhitespace);
- },
- node: token2
- });
- }
- };
- 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);
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "multiline-jsx-children": {
- 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({
- 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);
- },
- message: "Multiline JSX element text content must be on its own line",
- node: child
- });
- }
- 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({
- 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);
- },
- message: "First child of a multiline JSX element must be on a new line",
- node: firstChild
- });
- }
- 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({
- 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);
- },
- message: "Closing tag of a multiline JSX element must be on a new line",
- node: closingElement
- });
- }
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "ncore-sort-types": {
- create(context) {
- const sourceCode = context.getSourceCode();
- function sortMembers(node, members) {
- if (!members || members.length <= 1) return;
- const getLen = (n) => {
- let maxLen = 0;
- for (let i = n.loc.start.line - 1; i < n.loc.end.line; i++) {
- const lineLen = sourceCode.lines[i].trimRight().length;
- if (lineLen > maxLen) {
- maxLen = lineLen;
- }
- }
- return maxLen;
- };
- const initialSorted = members.map((member, i) => ({
- length: getLen(member),
- originalIndex: i,
- node: member
- })).sort((a, b) => {
- if (a.length !== b.length) return b.length - a.length;
- return a.originalIndex - b.originalIndex;
- });
- let changed = false;
- for (let i = 0; i < members.length; i++) {
- if (members[i] !== initialSorted[i].node) {
- changed = true;
- break;
- }
- }
- if (changed) {
- context.report({
- fix(fixer) {
- const fixes = [];
- for (let i = 0; i < members.length; i++) {
- fixes.push(fixer.replaceText(members[i], sourceCode.getText(initialSorted[i].node)));
- }
- return fixes;
- },
- message: "Type properties should be sorted by length (longest to shortest).",
- node: node
- });
- }
- }
- return {
- TSTypeLiteral(node) {
- sortMembers(node, node.members);
- },
- TSInterfaceBody(node) {
- sortMembers(node, node.body);
- }
- };
- },
- meta: {
- fixable: "code",
- type: "layout",
- schema: []
- }
- },
- "multiline-array-elements": {
- create(context) {
- const sourceCode = context.getSourceCode();
- return {
- "ArrayExpression, ArrayPattern"(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) {
- let isJSXProp = false;
- let current = node;
- while (current.parent) {
- if (current.parent.type === "JSXExpressionContainer") {
- isJSXProp = true;
- break;
- }
- current = current.parent;
- }
- if (!isJSXProp) return;
- }
- const firstElement = node.elements[0];
- if (openBracket.loc.end.line !== firstElement.loc.start.line - 1) {
- context.report({
- 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
- );
- },
- message: "First array element should be exactly on the next line",
- node: firstElement
- });
- }
- 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({
- 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
- );
- },
- node: next
- });
- }
- }
- const lastElement = node.elements[node.elements.length - 1];
- if (lastElement && lastElement.loc.end.line !== closingBracket.loc.start.line - 1) {
- context.report({
- 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
- );
- },
- message: "Array closing bracket should be exactly on the next line",
- node: closingBracket
- });
- }
- const tokenAfter = sourceCode.getTokenAfter(lastElement);
- if (tokenAfter && tokenAfter.value === "," && tokenAfter.range[1] <= closingBracket.range[0]) {
- context.report({
- message: "No trailing comma in arrays",
- fix(fixer) {
- return fixer.remove(tokenAfter);
- },
- node: lastElement
- });
- }
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "multiline-jsx-attributes": {
- 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({
- 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
- );
- },
- message: "First JSX attribute should be exactly on the next line",
- node: firstAttr
- });
- }
- 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({
- 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
- );
- },
- node: next
- });
- }
- }
- 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({
- 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
- );
- },
- message: "JSX closing slash should be exactly on the next line",
- node: slashToken
- });
- }
- } else {
- if (lastAttr.loc.end.line !== closingBracket.loc.start.line - 1) {
- context.report({
- 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
- );
- },
- message: "JSX closing bracket should be exactly on the next line",
- node: closingBracket
- });
- }
- }
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "multiline-object-properties": {
- 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({
- 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
- );
- },
- message: "First property should be exactly on the next line",
- node: firstProperty
- });
- }
- 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({
- 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
- );
- },
- node: next
- });
- }
- }
- }
- const lastProperty = properties[properties.length - 1];
- if (lastProperty.loc.end.line !== closingBrace.loc.start.line - 1) {
- context.report({
- 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
- );
- },
- message: "Object closing brace should be exactly on the next line",
- node: closingBrace
- });
- }
- const tokenAfter = sourceCode.getTokenAfter(lastProperty);
- if (tokenAfter && tokenAfter.value === ",") {
- context.report({
- message: "No trailing comma in objects",
- fix(fixer) {
- return fixer.remove(tokenAfter);
- },
- node: lastProperty
- });
- }
- }
- return {
- ObjectExpression(node) {
- checkProperties(node, node.properties);
- },
- ObjectPattern(node) {
- checkProperties(node, node.properties);
- },
- TSTypeLiteral(node) {
- checkProperties(node, node.members);
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- },
- "jsx-no-blank-lines": {
- create(context) {
- return {
- JSXText(node) {
- const text = node.value;
- if (!/^\s*$/.test(text)) {
- return;
- }
- const newlines = text.match(/\n/g);
- if (newlines && newlines.length > 1) {
- context.report({
- message: "No blank lines allowed between JSX elements",
- fix(fixer) {
- const match = text.match(/\n[ \t]*$/);
- if (match) {
- return fixer.replaceText(node, match[0]);
- }
- },
- node
- });
- }
- }
- };
- },
- meta: {
- fixable: "whitespace",
- type: "layout",
- schema: []
- }
- }
- };
|