From 8cc1eea358bd3c83f1232cf29d7b93cba5a89c5f Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Thu, 5 Dec 2024 09:24:11 +0100 Subject: [PATCH] fix: Detect more deprecated renderer declarations When a function is used, it can not have an apiVersion defined, so the declaration is deprecated and should be detected as such. --- src/linter/ui5Types/SourceFileLinter.ts | 11 +++- .../projects/sap.f/test/sap/f/LinterTest.js | 12 ++++- .../linter/rules/renderer/10Control.js | 8 +++ .../linter/rules/renderer/11Control.js | 11 ++++ .../linter/rules/renderer/2ControlRenderer.js | 2 +- .../linter/rules/renderer/9Control.js | 8 +++ .../lib/linter/rules/snapshots/renderer.ts.md | 51 ++++++++++++++++++ .../linter/rules/snapshots/renderer.ts.snap | Bin 5863 -> 6138 bytes 8 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/linter/rules/renderer/10Control.js create mode 100644 test/fixtures/linter/rules/renderer/11Control.js create mode 100644 test/fixtures/linter/rules/renderer/9Control.js diff --git a/src/linter/ui5Types/SourceFileLinter.ts b/src/linter/ui5Types/SourceFileLinter.ts index e86c61420..ded0a3512 100644 --- a/src/linter/ui5Types/SourceFileLinter.ts +++ b/src/linter/ui5Types/SourceFileLinter.ts @@ -385,6 +385,9 @@ export default class SourceFileLinter { return apiVersionNode; }; + const nodeType = this.checker.getTypeAtLocation(node); + const nodeValueDeclaration = nodeType.getSymbol()?.valueDeclaration; + // Analyze renderer property when it's an ObjectLiteralExpression // i.e. { renderer: {apiVersion: "2", render: () => {}} } if (node && (ts.isObjectLiteralExpression(node) || ts.isVariableDeclaration(node))) { @@ -420,7 +423,13 @@ export default class SourceFileLinter { this.analyzeIconCallInRenderMethod(node); // Analyze renderer property when it's a function i.e. { renderer: () => {} } } else if (ts.isMethodDeclaration(node) || ts.isArrowFunction(node) || - ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) { + ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node) || ( + nodeValueDeclaration && ( + ts.isFunctionExpression(nodeValueDeclaration) || + ts.isFunctionDeclaration(nodeValueDeclaration) || + ts.isArrowFunction(nodeValueDeclaration) + ) + )) { // reporter.addMessage() won't work in this case as it's bound to the current analyzed file. // The findings can be in different file i.e. Control being analyzed, // but reporting might be in ControlRenderer diff --git a/test/fixtures/linter/projects/sap.f/test/sap/f/LinterTest.js b/test/fixtures/linter/projects/sap.f/test/sap/f/LinterTest.js index bd2ae9084..40c6f6fb0 100644 --- a/test/fixtures/linter/projects/sap.f/test/sap/f/LinterTest.js +++ b/test/fixtures/linter/projects/sap.f/test/sap/f/LinterTest.js @@ -2,8 +2,16 @@ sap.ui.require([ "sap/f/Avatar", - "sap/m/DateTimeInput" -], (Avatar, DateTimeInput) => { + "sap/m/DateTimeInput", + "sap/f/ProductSwitchRenderer" +], (Avatar, DateTimeInput, ProductSwitchRenderer) => { new Avatar(); new DateTimeInput(); + + Avatar.extend("CustomControl", { + // Usage of render function without object is deprecated as no apiVersion is defined + // TODO detect: This is currently not detected as the module resolution is not working correctly. + // This needs to be solved in a separate change. + renderer: ProductSwitchRenderer.render + }); }); diff --git a/test/fixtures/linter/rules/renderer/10Control.js b/test/fixtures/linter/rules/renderer/10Control.js new file mode 100644 index 000000000..aa0f1de85 --- /dev/null +++ b/test/fixtures/linter/rules/renderer/10Control.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/Control", "./8ControlRenderer"], function (Control, Renderer) { + var myControl = Control.extend("mycomp.myControl", { + metadata: {}, + renderer: Renderer.render, + }); + + return myControl; +}); diff --git a/test/fixtures/linter/rules/renderer/11Control.js b/test/fixtures/linter/rules/renderer/11Control.js new file mode 100644 index 000000000..06f1a2a9a --- /dev/null +++ b/test/fixtures/linter/rules/renderer/11Control.js @@ -0,0 +1,11 @@ +sap.ui.define(["sap/ui/core/Control"], function (Control) { + function render(oRm, oMyControl) {} + const Renderer = {render}; + + var myControl = Control.extend("mycomp.myControl", { + metadata: {}, + renderer: Renderer.render, + }); + + return myControl; +}); diff --git a/test/fixtures/linter/rules/renderer/2ControlRenderer.js b/test/fixtures/linter/rules/renderer/2ControlRenderer.js index 242f3194d..049d7956f 100644 --- a/test/fixtures/linter/rules/renderer/2ControlRenderer.js +++ b/test/fixtures/linter/rules/renderer/2ControlRenderer.js @@ -3,7 +3,7 @@ sap.ui.define([], function () { myControlRenderer.apiVersion = 1; // Deprecated - myControlRenderer.render = function (oRm, oMyControl) {}; + myControlRenderer.render = (oRm, oMyControl) => {}; return myControlRenderer; }); diff --git a/test/fixtures/linter/rules/renderer/9Control.js b/test/fixtures/linter/rules/renderer/9Control.js new file mode 100644 index 000000000..a1cb36d67 --- /dev/null +++ b/test/fixtures/linter/rules/renderer/9Control.js @@ -0,0 +1,8 @@ +sap.ui.define(["sap/ui/core/Control", "./2ControlRenderer"], function (Control, Renderer) { + var myControl = Control.extend("mycomp.myControl", { + metadata: {}, + renderer: Renderer.render, + }); + + return myControl; +}); diff --git a/test/lib/linter/rules/snapshots/renderer.ts.md b/test/lib/linter/rules/snapshots/renderer.ts.md index 2ba67a1a4..61ca809ec 100644 --- a/test/lib/linter/rules/snapshots/renderer.ts.md +++ b/test/lib/linter/rules/snapshots/renderer.ts.md @@ -9,6 +9,40 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 [ + { + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + filePath: '10Control.js', + messages: [ + { + column: 13, + line: 4, + message: 'Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter in the renderer object', + messageDetails: '"Renderer Object (https://ui5.sap.com/#/topic/c9ab34570cc14ea5ab72a6d1a4a03e3f)",', + ruleId: 'no-deprecated-api', + severity: 2, + }, + ], + warningCount: 0, + }, + { + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + filePath: '11Control.js', + messages: [ + { + column: 13, + line: 7, + message: 'Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter in the renderer object', + messageDetails: '"Renderer Object (https://ui5.sap.com/#/topic/c9ab34570cc14ea5ab72a6d1a4a03e3f)",', + ruleId: 'no-deprecated-api', + severity: 2, + }, + ], + warningCount: 0, + }, { coverageInfo: [ { @@ -376,6 +410,23 @@ Generated by [AVA](https://avajs.dev). ], warningCount: 0, }, + { + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + filePath: '9Control.js', + messages: [ + { + column: 13, + line: 4, + message: 'Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter in the renderer object', + messageDetails: '"Renderer Object (https://ui5.sap.com/#/topic/c9ab34570cc14ea5ab72a6d1a4a03e3f)",', + ruleId: 'no-deprecated-api', + severity: 2, + }, + ], + warningCount: 0, + }, { coverageInfo: [], errorCount: 3, diff --git a/test/lib/linter/rules/snapshots/renderer.ts.snap b/test/lib/linter/rules/snapshots/renderer.ts.snap index 0e1f909bcc7ea28a25f619b8709807a9a19c92c8..95ce23e85903218be68b0e25aa95c66a2babc71b 100644 GIT binary patch literal 6138 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 M0cgkYkQ%W702DLdTmS$7 literal 5863 zcmV<9rOTUa7sn9SUS zfyvx)W|A;0BD)}qh#(@HY;~h*)#r09+PW3=J)cWotxIbcTVGqBwe`I!w&l)bZpgVX z16dkE#?J>oXYM`U|2@C+|DWZab7pdXWrbBSEpe_sEW0c&t9;7@*(N)T)?t!EwwYyz z?65d(M!R#7;&QIO-)uIzjAh?T>O?r|e})J{q6=6CtOK?Kdx2xXW59F3tH9g9hrnmR zpMmdy2m>S-AjJR!43KYtk)CT|T@9h59S)=0VDP2l;^HtwVMxFbZ7@tScpe^YFswI# z!Qe@rRFff|qXEpe7?d$vtW^ z%9DIYO-6f?v7NyX<4G2)$u6GcIyD*VN&Zkx#(9$8sL6OwvLF-;UA@Iolik!R3_lSH zPlUqTq3~5GToVT4!=OB@zBbTaa^iV&JV9_$CGty9j8m z4lU{e*LQ(=U0`JwIMM}v+6BH8(E=Ts7z;gPVMr{L#losscsLebi51WhI`nt3@TXXa ziG!>-D2;>LfeyW$13M29{V56{KJ&*I_Rc<9v? z%DaNQtANhbq5HbR;jVD9D}34&BD%r6Zcx)rKB3yH+a4qeB2E>C&0)AC`%B~ zavi!N0oEkIp#*q20e+VNnTaqVQ9y6ip^Fp2o(S6#;ju(`KM^{0hve>!(U9BGv7@`g zgzg}BhmGCgM0fbKJDd^OI|a6jr?ZPS7*el-M73uO?P`cIDYdf0SS1(RDiyV>N-%`@ z?nTKChvFEk)Yx2}OP$p|wo#6_N~6nY9WP`<$Q4m(vC7kou0_5J(K%xko6DhCvlcr& zB`Q6qL=n}p(`l@dor0B^I-|p8u~jvGkS5CAR8eASo2n^S(6p=hnjUX!M0d~ttnYjM$eg2&Q(G+We^9&IfuUl6sj`JxUtRa8qgtpY*Qi_O>cL0da2q2K~d z>+9x=yY}j~j>1C0V^gkfwy0a0DypTTut?CfwfUMJY_g+5S||#OE)azuG+$h&Yueah zLj;HQzoyxm#x+%w`o9)&*pLf2%-wu($J<(5KSA3#*<>|3j4q2}E4Rs2Mwg{l_UV1O z`Fg)-vfj{^(VKi>y*`=IJ)13aRL?dZ4a7<0IWlKjY*kjd{{Hx-cE#b6%|7k(o3DL! zleLGpjP{FkWc{MavRbH-CTg;@k)>YVcTIL$gdwbV5)4R!yd)Tr z1jR{En$&oGw^O6_-3UWSc@oG%rpU&b8r_TWbFYh^dvW4}l_aPU9vm-Z3R{%~>yu!6 z66{TaV@dFs040c)pG$(5giO~6nLSS1&3~Pl46$(J7ZOL$VG8_;xUMCj&!DM(;$TZSV(MF3A^-MDSFd5!RhIf`Y}{0${3vj6H;J$3d~P| zsuXYt250#(IA5&Tl_{`RFt9+#bk)|g8Fr??{uFp11)fNO7gOMk6nHlUK1qSkQ{b;D zKq^G1!d0ozD-|+Qp(qu`q(Vt5%uNMTD%esRKXjq+&g8dz&quV+UfUn{^4ad1rr92srrEAb(`-MKCfNQc&1cKIL3njF_UAHiV1zKsnS`}z#r-B&a4T3^k;H+?k& z-V2%ie88f9UZA1o`Zcb(_qD8%>6-1~>6&dNU8}jn=~~TsFT9uTW8?d4?BxDlR?D@a zzc1HI4Y|%VYZkA`(Jao&(Jb!B(Ja21qgnJ` zh|Kj_9G|OMT%D^~d?8n}_}5&)x0!kMCk#R@d7$LM_B=R|2XEvF+iac1t+LSCd{~hW z`|{!0e0V=!Ktgp$O#v(~fcpyInF4sPKtRHD$nHYeR|wA+!Uu)$b)kTS>yWKQu(JrB zD1x_(;EzQD5~17nTR#Nu7y=ItfmepWw4w0&Q276$0ve@5uO0>|!(jL@m^%!9H4Nrm z2X|a2pj~wZefT462e9a$kn=U2DI&=_q0=s3y7qvhIu!D@dj9R1Kf85^qT+& zCcs-01T;;D{%HdIWdd}Y2w4;1nTe2748~#s9iS`d>SEYf4Bsn;SI#286p;)avTYLF zJqezi1phS&{xnHIGIhwKli{h!@ZMzjYBI!45s)l>AWeZsroibb@c9%7D-n=vT{Rpi zfukkxatVA~0<&&}_ilvHsREj#ds^SAFlZ`Fo(h(!uwF!pbV%+r7%~m!PJ`NMaMv^e z8KOgGPKUDT;GPcmOou0?3&>C%GHC`(n*sJ2ux$oBG($j!>8fGOOqe(mDrdr)nb4&a z)|bLlr2;x!hrU+|KQD#9l|uJfu;uK(nAtFVwt!x*E9m^$P%#_U%!Wf}k>^EZlnz-t z2R6-t2j{?RbKqBV1Z12JxpOY;o(umr7v7x<|2tPe#_N!cWw5mjPL#o^GWe`aKyJ_> zt8RkzH^H%+;72#XCpQVm1Rb(&9&DZm56y$u=fSV%3CKiUWgnXl56_1;=EHC1L+%2w zFMvY}1ay)PJ-Gm0T>!sc0Nf0B+zg${A+KCOC+pCexW-xoqpBOEhAas`xB2hJi9nl))^+A7toJ1q-VLbhZw?tqL|&!OSp^dpfztxd zTLg5D4*k#qpIU&$khB#tx_K5bJ3Bp>X0=~xZMd4IN?<%eCiaC3LWBh!73LVbioTQ_?b&UOgiM28mOy*durgR8hE=# zK+HO1MJ=qUg~PRQvKBt96%bj6IP1V&2lv*&({=Dpoq$y8kR?mumZh*`DLlFqPAwIX zD*X)G4J+KR&kfJI;eEG&EYi<%mVt8_>|6$qFM~Ii35Z39$jf2La=2qTJh&WQSuP-p zb%=ch)UJTtE8xi$@SiILWQh)`S_#!FVe3kGcqP2HQb4Rar2JNpZ-w=@!jW6yhqnqy zwGLUl3hb-k&Qu(x=lb79a6R$%2&gx)v$jxJhNIr z>^j7}1{SY@&1>L+HSps#0&(;~J_3+|)0deV&SsP&923WoU?%e=SZ4i(eT~AZ85oT_LTQe;Ukz?lQk^B@!+gqIG&&_iH3B(Qhr*-eLF`yn`S2;MjZrT4?>`{B&}0=wlrWj+jP zhhfxVn12{PI}GJVVEYk)-Kt~59z6n29f5a_z!yhg;!#*}RN%JhxqV0B@KHE<6h1r( z1;@a0OyIVkN9Qrvehf|=gEx*r>2WxH9L^jU*q!>b+nvQ7T$9lyS1Atn1@?i%Op3Lp z+IE2xE1j(tn|y&|0O9qUy!D?$wlK?Ptgy~D;*u(4$t=5MN43Q!n_IauBK%i|VO0y%g~@7kIveW2^Ra(g zwcF%$dT+Iz{Kx>YaHu6>!D3Qu4f54b{nMOAd#0KimYr?1+ZBt=RV~|GX&I8O#%j%w zmP?*5rVaCalvcJ~1w{pjkGd9WE8}hE#w}&M&D_83WJd>(y|hJ|h2oUYZ5E1C_I3?{ zbcgxXCDzKhxp}pdo8`NhS?G`b}oN}$~u(*6d7IG0T5$~Ss67gtr z&?PP8lIHae{#udf&MSTf;jc0ROsB4d8H7LifhJJxc!2aLJHW)LjR(l)=@w7Uo~t`9 zK+`|ovJ*z9QY}wYJMC7PHu@j5x&-GD?QPKV7rElSD}K=OR~e$^D{#>ACqGd0ZO4O_ zKiL81ZI|_1T|)2`;&OYd^XHNPy=~AXZ8Jay*M)-%qrrvI4!SVfjCeQ9Q3Er(#*%GP z9CCKCNwG~+6ldwU=2%vMWrdG^uGn896n zeYC&I2(a|jG5TnK6%?pZx8pwApX@+G)8*VpH)91OIHzu%*PLc_LBV13@)$M)&F0(H zs&#O7aXHK`uGGQvO5WJI%8%~Y0I|QWC1T(1Q0%h9Ws&RG#TQSnksWSNSKM%`4V~Zh z%9v66AJ3Z+1%^!h6l?UfVk!N+j!eI}QM$*t24gPS>5}TRwZT*;cuuEX%{~J(*Wm24 z<=o)!FhEMMWweD${2kB954xl+Tq2GUmvZEwu-MZL30|r1s?yKx$mW(z8LL#=6{p1| zx4$vAIme0?og>5C@Uxm~ooPXrUf+$H16P<5Ousz<38OHz^YW?mMku(WHVOFQnk@7S!JVHol831Hq#=9 zVpD3Il2tL9Rf|fc*J!=xRXN#SIoUor*?KwNc*&CLgQ>|DB%{qN8J$i`l})m^)G`VR z>!*3{EdL=iuyF|0KR457C-WRHwsLF*S`}@n*vj=ETe++oCqUYL6j2_RkFEM^{%g;A?4Mxzv?9`(;Wfqsb zH~-7_yxRQCW1z+FcD&=|Pj;ZmYkS`D@~1n%vSmk#{M3#a`BDds{On-l`>JAa$Bg`( z4jTERf|2j5iqRc2^2c=0$S(;-zOO27?3j^1wSz{!xXIapBfqj^Mt)TXjePOwMF)=j znvNOywU>3|H~r*apo1bKe1DWvJWk`)mF}qj+nwoZv737QI{=Q&T$ker?+Xosw>+1nJ!bM#j&lj1GEYTM$< zl1h|Xb@AF=zYMKjcF0zbB$Hy6eJj}wC$H3XYL_A@wlQ+0;*e+fux!OPy3!>(v>dm-Gp` z