You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9 lines
124 KiB
JavaScript

3 months ago
import{ESLintUtils as ae,AST_NODE_TYPES as i,AST_TOKEN_TYPES as Rt}from"@typescript-eslint/utils";import j from"typescript";import{isAbsolute as Ws,posix as Hs}from"node:path";import{createRequire as Nt}from"node:module";import{DefinitionType as Bt}from"@typescript-eslint/scope-manager";const Vs="1.3.20",f=ae.RuleCreator(e=>`https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/${e}.md`),Ks=(e,s)=>e&&s?`${e}.${s}`:null,S=e=>e.type===i.FunctionExpression||e.type===i.ArrowFunctionExpression;function O(e){if(C(e))return y(e);switch(e.type){case i.TaggedTemplateExpression:return O(e.tag);case i.MemberExpression:return Ks(O(e.object),O(e.property));case i.NewExpression:case i.CallExpression:return O(e.callee)}return null}const C=(e,s)=>N(e,s)||B(e,s),N=(e,s)=>e.type===i.Identifier&&(s===void 0||e.name===s),Gs=(e,s)=>e.type===i.TemplateLiteral&&e.quasis.length===1&&(s===void 0||e.quasis[0].value.raw===s),Xs=(e,s)=>e.type===i.Literal&&typeof e.value=="string"&&(s===void 0||e.value===s),B=(e,s)=>Xs(e,s)||Gs(e,s),y=e=>e.type===i.Identifier?e.name:W(e),W=e=>e?.type===i.TemplateLiteral?e.quasis[0].value.raw:e?.value,X=(e,s,t)=>e.replaceText(s,s.type===i.Identifier?t:`'${t}'`),Lt=(e,s,t,r)=>{const n=t.arguments[r],o=t.arguments[t.arguments.length-1],{sourceCode:a}=s;let c=a.getTokenAfter(o);return c.value===","&&(c=a.getTokenAfter(c)),e.removeRange([n.range[0],c.range[0]])},zs=(e,s)=>y(e.matcher)==="toBeInstanceOf"&&e.args.length===1&&C(e.args[0],s);var _=(e=>(e.vi="vi",e.vitest="vitest",e))(_||{}),H=(e=>(e.describe="describe",e.fdescribe="fdescribe",e.xdescribe="xdescribe",e))(H||{}),A=(e=>(e.fit="fit",e.it="it",e.test="test",e.xit="xit",e.xtest="xtest",e.bench="bench",e))(A||{}),Ft=(e=>(e.beforeAll="beforeAll",e.beforeEach="beforeEach",e.afterAll="afterAll",e.afterEach="afterEach",e))(Ft||{}),R=(e=>(e.to="to",e.have="have",e.not="not",e.rejects="rejects",e.resolves="resolves",e.returns="returns",e.branded="branded",e.asserts="asserts",e.constructorParameters="constructorParameters",e.parameters="parameters",e.thisParameter="thisParameter",e.guards="guards",e.instance="instance",e.items="items",e))(R||{}),L=(e=>(e.toBe="toBe",e.toEqual="toEqual",e.toStrictEqual="toStrictEqual",e))(L||{});function Pt(e){return e.getCallSignatures().length>0?!0:e.getSymbol()?.getDeclarations()?.some(s=>j.isArrowFunction(s)||j.isClassDeclaration(s)||j.isClassExpression(s)||j.isFunctionDeclaration(s)||j.isFunctionExpression(s)||j.isMethodDeclaration(s)||j.isFunctionTypeNode(s))??!1}const Ys=new Set(["beforeEach","beforeAll","afterEach","afterAll","it","it.skip","it.only","it.concurrent","it.sequential","it.todo","it.fails","it.extend","it.skipIf","it.runIf","it.each","it.skip.only","it.skip.concurrent","it.skip.sequential","it.skip.todo","it.skip.fails","it.only.skip","it.only.concurrent","it.only.sequential","it.only.todo","it.only.fails","it.concurrent.skip","it.concurrent.only","it.concurrent.sequential","it.concurrent.todo","it.concurrent.fails","it.sequential.skip","it.sequential.only","it.sequential.concurrent","it.sequential.todo","it.sequential.fails","it.todo.skip","it.todo.only","it.todo.concurrent","it.todo.sequential","it.todo.fails","it.fails.skip","it.fails.only","it.fails.concurrent","it.fails.sequential","it.fails.todo","it.extend.skip","it.extend.only","it.extend.concurrent","it.extend.sequential","it.extend.todo","it.extend.fails","it.skipIf.skip","it.skipIf.only","it.skipIf.concurrent","it.skipIf.sequential","it.skipIf.todo","it.skipIf.fails","it.runIf.skip","it.runIf.only","it.runIf.concurrent","it.runIf.sequential","it.runIf.todo","it.runIf.fails","it.skip.each","it.only.each","it.concurrent.each","it.sequential.each","it.todo.each","it.fails.each","it.extend.skipIf","it.extend.runIf","it.extend.each","it.skipIf.each","it.runIf.each","it.skip.only.concurrent","it.skip.only.sequential","it.skip.only.todo","it.skip.only.fails","it.skip.concurrent.only","it.skip.concurrent.sequential","it.skip.concurrent.todo","it.skip.concurrent.fails","it.skip.sequential.only","it.skip.sequential.concurrent","it.skip.sequential.to
`+e.sourceCode.getText(r)+";"):yield a.insertTextAfterRange([0,0],e.sourceCode.getText(r)+`;
`)}});const o=r.callee.property;o.name==="mock"&&n.push({messageId:"suggestReplaceMockWithDoMock",fix(a){return a.replaceText(o,"doMock")}}),e.report({node:r,messageId:"hoistedApisOnTop",suggest:n})}}}}}),ke="consistent-test-it",Ht=(e,s,t)=>r=>[r.replaceText(e.type===i.MemberExpression?e.object:e,Tn(s,t))];function Tn(e,s){return e===A.fit?"test.only":e.startsWith("f")||e.startsWith("x")?e.charAt(0)+s:s}function be(e){return e===A.test?A.it:A.test}const wn=f({name:ke,meta:{type:"suggestion",fixable:"code",docs:{description:"enforce using test or it but not both",recommended:!1},messages:{consistentMethod:"Prefer using {{ testFnKeyWork }} instead of {{ oppositeTestKeyword }}",consistentMethodWithinDescribe:"Prefer using {{ testKeywordWithinDescribe }} instead of {{ oppositeTestKeyword }} within describe"},schema:[{type:"object",properties:{fn:{type:"string",enum:[A.test,A.it]},withinDescribe:{type:"string",enum:[A.test,A.it]}},additionalProperties:!1}]},defaultOptions:[{}],create(e,s){const{fn:t,withinDescribe:r}=s[0],n=t||A.test,o=r||t||A.it,a=n===o?n:void 0;let c=0;return{ImportDeclaration(l){if(a==null||l.source.type!=="Literal"||l.source.value!=="vitest")return;const u=be(a);for(const p of l.specifiers)p.type==="ImportSpecifier"&&p.imported.type==="Identifier"&&p.local.name===p.imported.name&&p.local.name===u&&e.report({node:p,data:{testFnKeyWork:n,oppositeTestKeyword:u},messageId:"consistentMethod",fix:d=>{const x=l.specifiers.filter(m=>m.local.name!==u);if(x.length>0){const m=x.map(h=>h.local.name).join(", "),I=l.specifiers.at(-1)?.range;return I?d.replaceTextRange([l.specifiers[0].range[0],I[1]],m):null}return d.replaceText(p.local,a)}})},CallExpression(l){if(l.callee.type===i.Identifier&&l.callee.name==="bench")return;const u=g(l,e);if(!u)return;if(u.type==="describe"){c++;return}const p=l.callee.type===i.TaggedTemplateExpression?l.callee.tag:l.callee.type===i.CallExpression?l.callee.callee:l.callee;if(u.type==="test"&&c===0&&!u.name.endsWith(n)){const d=be(n);e.report({node:l.callee,data:{testFnKeyWork:n,oppositeTestKeyword:d},messageId:"consistentMethod",fix:Ht(p,u.name,n)})}else if(u.type==="test"&&c>0&&!u.name.endsWith(o)){const d=be(o);e.report({messageId:"consistentMethodWithinDescribe",node:l.callee,data:{testKeywordWithinDescribe:o,oppositeTestKeyword:d},fix:Ht(p,u.name,o)})}},"CallExpression:exit"(l){w(l,e,["describe"])&&c--}}}}),xe="consistent-vitest-vi",An=e=>e===_.vi?_.vitest:_.vi,vn=f({name:xe,meta:{type:"suggestion",fixable:"code",docs:{description:"enforce using vitest or vi but not both",recommended:!1},messages:{consistentUtil:"Prefer using {{ utilKeyword }} instead of {{ oppositeUtilKeyword }}"},schema:[{type:"object",properties:{fn:{type:"string",enum:[_.vi,_.vitest],default:_.vi}},additionalProperties:!1}]},defaultOptions:[{fn:_.vi}],create(e,s){const t=s[0].fn,r=An(t);return{ImportDeclaration(n){if(!(n.source.type!==i.Literal||n.source.value!=="vitest"))for(const o of n.specifiers)o.type===i.ImportSpecifier&&o.imported.type===i.Identifier&&o.local.name===o.imported.name&&o.imported.name===r&&e.report({node:o,messageId:"consistentUtil",data:{utilKeyword:t,oppositeUtilKeyword:r},fix:a=>{const c=n.specifiers.filter(l=>l.local.name!==r);if(c.length>0){const l=c.map(p=>p.local.name).join(", "),u=n.specifiers.at(-1)?.range;return u?a.replaceTextRange([n.specifiers[0].range[0],u[1]],l):null}return a.replaceText(o.local,t)}})},CallExpression(n){if(g(n,e)?.type!==r)return;const o=n.callee.type===i.MemberExpression?n.callee.object:n.callee;e.report({node:o,data:{utilKeyword:t,oppositeUtilKeyword:r},messageId:"consistentUtil",fix:a=>a.replaceText(o,t)})}}}}),Ie="prefer-to-be",Cn=e=>e.type===i.Literal&&e.value===null,Mn=e=>Cn(P(e)),Vt=(e,s)=>N(P(e),s),Sn=e=>Math.floor(e)!==Math.ceil(e),$n=e=>{let s=P(e);return s.type===i.Literal&&typeof s.value=="number"&&Sn(s.value)?!1:(s.type===i.UnaryExpression&&s.operator==="-"&&(s=s.argument),s.type===i.Literal?!("regex"in s):s.type===i.TemplateLiteral)},K=(e,s,t,r,n)=>{e.report({messageId:`useToBe${s}`,fix(o){const a=[X(o,t.matcher,`toBe${s}`)];return t.args?.lengt
`);if(t.find(d=>d.type==="ImportNamespaceSpecifier"))return c.insertTextBefore(l.body[0],`import { ${o} } from 'vitest';
`);const u=t.find(d=>d.type==="ImportDefaultSpecifier");if(u)return c.insertTextAfter(u,`, { ${o} }`);const p=t[t.length-1];return c.insertTextAfter(p,`, ${o}`)}})}}}}),Ne="no-conditional-in-test",Kn=f({name:Ne,meta:{docs:{description:"disallow conditional tests",requiresTypeChecking:!1,recommended:!1},messages:{noConditionalInTest:"Remove conditional tests"},schema:[],type:"problem"},defaultOptions:[],create(e){return{IfStatement(s){s.parent?.parent?.parent?.type==="CallExpression"&&w(s.parent?.parent?.parent,e,["test","it"])&&e.report({messageId:"noConditionalInTest",node:s})}}}}),Be="no-disabled-tests",Gn=f({name:Be,meta:{type:"suggestion",docs:{description:"disallow disabled tests",recommended:!1},messages:{missingFunction:"Test is missing function argument",pending:"Call to pending()",pendingSuite:"Call to pending() within test suite",pendingTest:"Call to pending() within test",disabledSuite:"Disabled test suite - if you want to skip a test suite temporarily, use .todo() instead",disabledTest:"Disabled test - if you want to skip a test temporarily, use .todo() instead"},schema:[]},defaultOptions:[],create(e){let s=0,t=0;return{CallExpression(r){const n=g(r,e);if(!n)return;n.type==="describe"&&s++,n.type==="test"&&(t++,r.arguments.length<2&&n.members.every(a=>y(a)==="skip")&&e.report({messageId:"missingFunction",node:r}));const o=n.members.find(a=>y(a)==="skip");(n.name.startsWith("x")||o!==void 0)&&e.report({messageId:n.type==="describe"?"disabledSuite":"disabledTest",node:o??n.head.node})},"CallExpression:exit"(r){const n=g(r,e);n&&(n.type==="describe"&&s--,n.type==="test"&&t--)},'CallExpression[callee.name="pending"]'(r){const n=ce(e,r);Dt(n,"pending")||(t>0?e.report({messageId:"pendingTest",node:r}):s>0?e.report({messageId:"pendingSuite",node:r}):e.report({messageId:"pending",node:r}))}}}}),Le="no-done-callback",Xn=(e,s,t)=>{if(s)return e.arguments[1];const r=g(e,t);return r?.type==="hook"&&e.arguments.length>=1?e.arguments[0]:r?.type==="test"&&e.arguments.length>=2?e.arguments[1]:null},zn=f({name:Le,meta:{type:"suggestion",docs:{description:"disallow using a callback in asynchronous tests and hooks",recommended:!1},deprecated:!0,schema:[],messages:{noDoneCallback:"Return a promise instead of relying on callback parameter",suggestWrappingInPromise:"Wrap in `new Promise({{ callback }} => ...`",useAwaitInsteadOfCallback:"Use `await` instead of callback in async function"},hasSuggestions:!0},defaultOptions:[],create(e){return{CallExpression(s){const t=/\.each$|\.concurrent$/.test(O(s.callee)??"");if(t&&s.callee.type!==i.TaggedTemplateExpression||e.sourceCode.getAncestors(s).some(a=>a.type!==i.CallExpression||!w(a,e,["describe","test"])?!1:a.callee.type===i.MemberExpression&&C(a.callee.property,"concurrent")))return;const r=Xn(s,t,e),n=Number(t);if(!r||!S(r)||r.params.length!==1+n)return;const o=r.params[n];if(o.type!==i.Identifier){e.report({node:o,messageId:"noDoneCallback"});return}if(r.async){e.report({node:o,messageId:"useAwaitInsteadOfCallback"});return}e.report({node:s,messageId:"noDoneCallback",suggest:[{messageId:"suggestWrappingInPromise",data:{callback:o.name},fix(a){const{body:c,params:l}=r,{sourceCode:u}=e,p=u.getFirstToken(c),d=u.getLastToken(c),[x]=l,m=l[l.length-1],I=u.getTokenBefore(x);let h=u.getTokenAfter(m);if(h?.value===","&&(h=u.getTokenAfter(h)),!p||!d||!I||!h)throw new Error(`Unexpected null when attempting to fix ${e.filename} - please file an issue at https://github/veritem/eslint-plugin-vitest`);let b=a.replaceText(x,"()");I.value==="("&&h.value===")"&&(b=a.removeRange([I.range[1],h.range[0]]));let q=`new Promise(${o.name} => `,T=")",E=!0;return c.type===i.BlockStatement&&(q=`return ${q}{`,T+="}",E=!1),[b,E?a.insertTextBefore(p,q):a.insertTextAfter(p,q),a.insertTextAfter(d,T)]}}]})}}}}),Fe="no-duplicate-hooks",Yn=f({name:Fe,meta:{docs:{recommended:!1,description:"disallow duplicate hooks and teardown hooks",requiresTypeChecking:!1},messages:{noDuplicateHooks:"Duplicate {{ hook }} in describe block"},schema:[],type:"suggestion"},defaultOptions:[],create(e){const s=[{}];return{CallExpression
`:`
`;return o.insertTextAfter(a,l)}})},mo={0:()=>!0,1:fo},yo=()=>{let e=null;return{get prevNode(){return e.prevNode},set prevNode(s){e.prevNode=s},enter(){e={upper:e,prevNode:null}},exit(){e=e.upper}}},$=e=>(s,t)=>{let r=s;if(r.type===i.ExpressionStatement){r.expression.type===i.AwaitExpression&&(r=r.expression.argument);const n=t.getFirstToken(r);return n?.type===Rt.Identifier&&n.value===e}return!1},ho={0:()=>!0,1:$("afterAll"),2:$("afterEach"),3:$("beforeAll"),4:$("beforeEach"),5:$("describe"),6:$("expect"),7:$("expectTypeOf"),8:$("fdescribe"),9:$("fit"),10:$("it"),11:$("test"),12:$("xdescribe"),13:$("xit"),14:$("xtest")},mt=(e,s,t)=>{let r=e;const{sourceCode:n}=t;for(;r.type===i.LabeledStatement;)r=r.body;return Array.isArray(s)?s.some(o=>mt(r,o,t)):ho[s](r,n)},go=(e,s,t)=>{const{configs:r}=t,n=o=>mo[o](e,s,t);for(let o=r.length-1;o>=0;--o){const{prevStatementType:a,nextStatementType:c,paddingType:l}=r[o];if(mt(e,a,t)&&mt(s,c,t))return n(l)}return n(0)},ws=(e,s)=>{const{scopeInfo:t}=s;Nr(e?.parent.type)&&(t.prevNode&&go(t.prevNode,e,s),t.prevNode=e)},D=(e,s,t,r=!1)=>f({name:e,meta:{docs:{description:s},fixable:"whitespace",deprecated:r,messages:{missingPadding:"expect blank line before this statement"},schema:[],type:"suggestion"},defaultOptions:[],create(n){const o={ruleContext:n,sourceCode:n.sourceCode??n.getSourceCode(),scopeInfo:yo(),configs:t},{scopeInfo:a}=o;return{Program:a.enter,"Program:exit":a.exit,BlockStatement:a.enter,"BlockStatement:exit":a.exit,SwitchStatement:a.enter,"SwitchStatement:exit":a.exit,":statement":c=>ws(c,o),SwitchCase(c){ws(c,o),a.enter()},"SwitchCase:exit":a.exit}}}),yt="padding-around-after-all-blocks",As=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:k.AfterAllToken},{paddingType:M.Always,prevStatementType:k.AfterAllToken,nextStatementType:k.Any}],ko=D(yt,"Enforce padding around `afterAll` blocks",As),ht="padding-around-after-each-blocks",vs=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:k.AfterEachToken},{paddingType:M.Always,prevStatementType:k.AfterEachToken,nextStatementType:k.Any}],bo=D(ht,"Enforce padding around `afterEach` blocks",vs),gt="padding-around-before-all-blocks",Cs=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:k.BeforeAllToken},{paddingType:M.Always,prevStatementType:k.BeforeAllToken,nextStatementType:k.Any}],xo=D(gt,"Enforce padding around `beforeAll` blocks",Cs),kt="padding-around-before-each-blocks",Ms=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:k.BeforeEachToken},{paddingType:M.Always,prevStatementType:k.BeforeEachToken,nextStatementType:k.Any}],Io=D(kt,"Enforce padding around `beforeEach` blocks",Ms),bt="padding-around-describe-blocks",Ss=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:[k.DescribeToken,k.FdescribeToken,k.XdescribeToken]},{paddingType:M.Always,prevStatementType:[k.DescribeToken,k.FdescribeToken,k.XdescribeToken],nextStatementType:k.Any}],Eo=D(bt,"Enforce padding around `describe` blocks",Ss),xt="padding-around-expect-groups",$s=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:k.ExpectToken},{paddingType:M.Always,prevStatementType:k.ExpectToken,nextStatementType:k.Any},{paddingType:M.Any,prevStatementType:k.ExpectToken,nextStatementType:k.ExpectToken},{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:k.ExpectTypeOfToken},{paddingType:M.Always,prevStatementType:k.ExpectTypeOfToken,nextStatementType:k.Any},{paddingType:M.Any,prevStatementType:k.ExpectTypeOfToken,nextStatementType:k.ExpectTypeOfToken}],qo=D(xt,"Enforce padding around `expect` groups",$s),It="padding-around-test-blocks",Os=[{paddingType:M.Always,prevStatementType:k.Any,nextStatementType:[k.TestToken,k.ItToken,k.FitToken,k.XitToken,k.XtestToken]},{paddingType:M.Always,prevStatementType:[k.TestToken,k.ItToken,k.FitToken,k.XitToken,k.XtestToken],nextStatementType:k.Any}],To=D(It,"Enforce padding around `test` blocks",Os),Et="padding-around-all",wo=D(Et,"Enforce padding around vitest functions",[...As,...vs,...Cs,...Ms,...Ss,...$s,...Os]),qt="valid-expect-in-promise",Ao=[