@webpieces/nx-webpieces-rules 0.4.809 → 0.4.811
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/executors.json +10 -0
- package/package.json +7 -7
- package/src/executors/docs-generate/executor.d.ts +47 -0
- package/src/executors/docs-generate/executor.js +102 -0
- package/src/executors/docs-generate/executor.js.map +1 -0
- package/src/executors/docs-generate/schema.json +21 -0
- package/src/executors/openapi-generate/executor.d.ts +46 -0
- package/src/executors/openapi-generate/executor.js +94 -0
- package/src/executors/openapi-generate/executor.js.map +1 -0
- package/src/executors/openapi-generate/schema.json +18 -0
- package/src/lib/api-usage/api-doc-rules-scan.d.ts +22 -14
- package/src/lib/api-usage/api-doc-rules-scan.js +32 -21
- package/src/lib/api-usage/api-doc-rules-scan.js.map +1 -1
- package/src/lib/api-usage/api-doc-rules-verdicts.d.ts +14 -1
- package/src/lib/api-usage/api-doc-rules-verdicts.js +19 -4
- package/src/lib/api-usage/api-doc-rules-verdicts.js.map +1 -1
- package/src/lib/api-usage/api-doc-rules.d.ts +42 -5
- package/src/lib/api-usage/api-doc-rules.js +65 -9
- package/src/lib/api-usage/api-doc-rules.js.map +1 -1
- package/src/lib/api-usage/api-scanner.d.ts +5 -5
- package/src/lib/api-usage/api-scanner.js +2 -2
- package/src/lib/api-usage/api-scanner.js.map +1 -1
- package/src/lib/generated-docs/generator-target.d.ts +54 -0
- package/src/lib/generated-docs/generator-target.js +145 -0
- package/src/lib/generated-docs/generator-target.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-doc-rules-scan.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-scan.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;;;;AAEH,+CAAyB;AACzB,mDAA6B;AAC7B,uDAAiC;AACjC,4DASkC;AAClC,0DAAyD;AAEzD,uCAAuD;AACvD,mDASyB;AACzB,qEAWkC;AAElC,0FAA0F;AAC1F,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEzC,uFAAuF;AACvF,MAAM,YAAY;IAEM;IACA;IACA;IAHpB,YACoB,OAAe,EACf,OAAgB,EAChB,GAAY;QAFZ,YAAO,GAAP,OAAO,CAAQ;QACf,YAAO,GAAP,OAAO,CAAS;QAChB,QAAG,GAAH,GAAG,CAAS;IAC7B,CAAC;CACP;AAED,oGAAoG;AACpG,MAAM,IAAI;IAEc;IACA;IAFpB,YACoB,OAAe,EACf,IAAY;QADZ,YAAO,GAAP,OAAO,CAAQ;QACf,SAAI,GAAJ,IAAI,CAAQ;IAC7B,CAAC;IAEJ,0EAA0E;IAC1E,UAAU,CAAC,aAAqB;QAC5B,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IACxE,CAAC;IAED,2FAA2F;IAC3F,8EAA8E;IAC9E,MAAM,CAAC,KAAK,CAAC,QAAgB,EAAE,QAAgB;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACjD,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACjD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,eAAe;IAIY;IAHZ,UAAU,GAAwB,EAAE,CAAC;IACrC,UAAU,GAAwB,EAAE,CAAC;IAEtD,YAA6B,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAEjD,uFAAuF;IACvF,IAAI;QACA,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,GAAG,CAAC,MAAyB,EAAE,IAAU;QACrC,MAAM,OAAO,GAAG,8BAAc,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9E,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC7B,OAAO;QACX,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAED,QAAQ;QACJ,OAAO,IAAI,+BAAe,CACtB,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,EAC9C,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CACjD,CAAC;IACN,CAAC;IAED;;;;OAIG;IACH,uFAAuF;IAC/E,MAAM,CAAC,aAAa,CAAC,KAAmC;QAC5D,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAClB,CAAC,CAAoB,EAAE,CAAoB,EAAE,EAAE,CAC3C,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CACtD,CAAC;IACN,CAAC;CACJ;AAED;;;;;;;GAOG;AACH,MAAM,UAAU;IAES;IACA;IAFrB,YACqB,OAAoC,EACpC,GAAgC;QADhC,YAAO,GAAP,OAAO,CAA6B;QACpC,QAAG,GAAH,GAAG,CAA6B;IAClD,CAAC;IAEJ;;;;;;;;OAQG;IACH,MAAM,CAAC,KAA0C,EAAE,IAAU;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC;QACxC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAED,sDAAsD;IACtD,OAAO,CAAC,MAAyB,EAAE,IAAU;QACzC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,WAAW;QACP,OAAO,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;IAChE,CAAC;IAED,iGAAiG;IACjG,OAAO;QACH,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;IAClC,CAAC;CACJ;AAED;;;;;;;;GAQG;AACH,MAAa,eAAe;IAWH;IACA;IAEA;IACA;IAdrB;;;;;;OAMG;IACc,UAAU,GAAmB,EAAE,CAAC;IAEjD,YACqB,aAAqB,EACrB,YAAsC;IACvD,4FAA4F;IAC3E,cAA0B,0BAAU,CAAC,GAAG,CAAC,4BAAY,CAAC,EACtD,UAAsB,0BAAU,CAAC,GAAG,CAAC,wBAAQ,CAAC;QAJ9C,kBAAa,GAAb,aAAa,CAAQ;QACrB,iBAAY,GAAZ,YAAY,CAA0B;QAEtC,gBAAW,GAAX,WAAW,CAA2C;QACtD,YAAO,GAAP,OAAO,CAAuC;IAChE,CAAC;IAEJ,GAAG;QACC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE,OAAO,mCAAmB,CAAC,KAAK,EAAE,CAAC;QAC3F,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,mCAAmB,CAAC,KAAK,EAAE,CAAC;QAE3D,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,4BAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,wBAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7E,MAAM,OAAO,GAAG,EAAE,CAAC,aAAa,CAC5B,KAAK,CAAC,GAAG,CAAC,CAAC,IAAkB,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAC/C,IAAI,CAAC,eAAe,EAAE,CACzB,CAAC;QACF,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,IAAI,UAAU,CACvB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAClC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAC7B,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,mCAAmB,CAC1B,OAAO,EAAE,QAAQ,EAAE,IAAI,IAAI,+BAAe,CAAC,EAAE,EAAE,EAAE,CAAC,EAClD,GAAG,EAAE,QAAQ,EAAE,IAAI,IAAI,+BAAe,CAAC,EAAE,EAAE,EAAE,CAAC,EAC9C,IAAI,CAAC,UAAU,CAClB,CAAC;IACN,CAAC;IAED,8FAA8F;IACtF,SAAS,CACb,IAAkB,EAClB,OAAmB,EACnB,IAAgB,EAChB,SAA8B;QAE9B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,oIAAoI;QACpI,IAAI,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,IAAI,+BAAe,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;gBACxE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;YACpD,CAAC;QACL,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,6BAA6B;YAC7B,IAAI,CAAC,CAAC,GAAG,YAAY,qCAAqB,CAAC;gBAAE,MAAM,GAAG,CAAC;YACvD,IAAI,CAAC,uBAAuB,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAC1D,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,uBAAuB,CAC3B,KAA4B,EAC5B,IAAkB,EAClB,MAAqB,EACrB,IAAgB;QAEhB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CACP,GAAsB,EAAE,CAAC,IAAI,iCAAiB,CAC1C,IAAA,wCAAe,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EACzD,EAAE,EACF,wCAAwC,KAAK,CAAC,OAAO,EAAE,EACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EACnC,KAAK,CAAC,IAAI,EACV,EAAE,CACL,EACD,IAAI,CACP,CAAC;IACN,CAAC;IAED,uEAAuE;IAC/D,UAAU,CACd,KAAkB,EAClB,MAAqB,EACrB,IAAgB,EAChB,SAA8B;QAE9B,MAAM,KAAK,GAAG,IAAA,wCAAe,EAAC,MAAM,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;QAC1D,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC3D,CAAC;IAED,8FAA8F;IACtF,aAAa,CAAC,KAAkB,EAAE,IAAgB,EAAE,QAAgB;QACxE,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACrD,MAAM,OAAO,GAAG,IAAA,yCAAgB,EAAC,QAAQ,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CACP,GAAsB,EAAE,CAAC,IAAI,iCAAiB,CAC1C,KAAK,CAAC,YAAY,EAClB,EAAE,EACF,OAAO,CAAC,IAAI,EACZ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EACnC,OAAO,CAAC,IAAI,EACZ,KAAK,CAAC,QAAQ,CACjB,EACD,IAAI,CACP,CAAC;QACN,CAAC;IACL,CAAC;IAED,+FAA+F;IACvF,kBAAkB,CAAC,KAAkB,EAAE,IAAgB,EAAE,QAAgB;QAC7E,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACtC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAA,uCAAc,EAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBAClD,IAAI,CAAC,MAAM,CACP,CAAC,IAAY,EAAqB,EAAE,CAChC,IAAI,iCAAiB,CACjB,KAAK,CAAC,YAAY,EAClB,EAAE,EACF,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,yCAAyC;oBAChE,wCAAwC,EAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EACnC,IAAA,yCAAgB,EAAC,IAAI,CAAC,EACtB,KAAK,CAAC,QAAQ,CACjB,EACL,IAAI,CACP,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACK,iBAAiB,CACrB,KAAkB,EAClB,MAAqB,EACrB,KAAoB,EACpB,IAAgB;QAEhB,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACrC,IAAI,QAAQ,CAAC,IAAI,KAAK,iCAAQ;gBAAE,SAAS;YACzC,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,IAAA,mCAAU,EAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAChF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;YAC1E,IAAI,CAAC,MAAM,CACP,GAAsB,EAAE,CAAC,IAAI,iCAAiB,CAC1C,KAAK,CAAC,YAAY,EAClB,QAAQ,CAAC,UAAU,EACnB,4EAA4E;gBACxE,cAAc,EAClB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EACnC,sCAAa,EACb,KAAK,CAAC,QAAQ,CACjB,EACD,IAAI,CACP,CAAC;QACN,CAAC;IACL,CAAC;IAED,iGAAiG;IACzF,UAAU,CACd,KAAkB,EAClB,MAAqB,EACrB,KAAoB,EACpB,IAAgB,EAChB,SAA8B;QAE9B,MAAM,QAAQ,GAAG,IAAI,iCAAiB,CAAC,KAAK,CAAC,CAAC;QAC9C,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACrC,IAAI,QAAQ,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;gBACvC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;oBAAE,SAAS;gBAC9B,gFAAgF;gBAChF,qFAAqF;gBACrF,qFAAqF;gBACrF,IAAI,CAAC,UAAU,CAAC,IAAI,CAChB,IAAI,4BAAY,CACZ,KAAK,CAAC,YAAY,EAClB,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,aAAa,EACtB,IAAI,IAAI,CACJ,MAAM,CAAC,QAAQ,EACf,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CACpC,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,CACnC,CACJ,CAAC;gBACF,SAAS;YACb,CAAC;YACD,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS;gBAAE,SAAS;YAC7C,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;YAC1E,KAAK,MAAM,OAAO,IAAI,IAAA,qCAAY,EAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC;gBACvE,IAAI,CAAC,OAAO,CACR,IAAI,iCAAiB,CACjB,KAAK,CAAC,YAAY,EAClB,QAAQ,CAAC,UAAU,EACnB,OAAO,CAAC,IAAI,EACZ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EACnC,OAAO,CAAC,IAAI,EACZ,KAAK,CAAC,QAAQ,CACjB,EACD,IAAI,CACP,CAAC;YACN,CAAC;QACL,CAAC;QACD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,oBAAoB,EAAE,CAAC;YAClD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACjE,IAAI,CAAC,OAAO,CACR,IAAI,iCAAiB,CACjB,KAAK,CAAC,YAAY,EAClB,UAAU,EACV,wEAAwE,EACxE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EACnC,wEAAwE,EACxE,KAAK,CAAC,QAAQ,CACjB,EACD,IAAI,CACP,CAAC;QACN,CAAC;IACL,CAAC;IAED,+FAA+F;IACvF,aAAa;QACjB,MAAM,KAAK,GAAmB,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG;gBAAE,SAAS;YACpD,MAAM,OAAO,GACT,IAAI,CAAC,WAAW,CAAC,OAAO;gBACxB,CAAC,IAAA,6BAAc,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;YAC9D,MAAM,GAAG,GACL,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,IAAA,6BAAc,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAClF,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG;gBAAE,SAAS;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;YAC7E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAS;YACrC,KAAK,MAAM,IAAI,IAAI,IAAA,wBAAc,EAAC,MAAM,CAAC,EAAE,CAAC;gBACxC,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC;oBAAE,SAAS,CAAC,wCAAwC;gBACxE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oBAAE,SAAS;gBACrE,KAAK,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YACrD,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAe,EAAE,CAAe,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAChG,CAAC;IAED;;;;;OAKG;IACK,eAAe;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,CAAC;QACjE,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAChC,CAAC,CAAC,EAAE,CAAC,0BAA0B,CACzB,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,EAC/C,EAAE,CAAC,GAAG,EACN,IAAI,CAAC,aAAa,CACrB,CAAC,OAAO;YACX,CAAC,CAAC,EAAE,CAAC;QACT,OAAO;YACH,GAAG,QAAQ;YACX,MAAM,EAAE,IAAI;YACZ,YAAY,EAAE,IAAI;YAClB,KAAK,EAAE,EAAE;YACT,sBAAsB,EAAE,IAAI;SAC/B,CAAC;IACN,CAAC;CACJ;AA9RD,0CA8RC","sourcesContent":["/**\n * `api-rules-for-openapi` and `api-rules-for-mcp` (#1011) — the CI half of \"is this contract\n * publishable\", on EVERY `@ApiPath` class in the workspace, `@ApiType` or not.\n *\n * ## The acceptance contract, and why this file drives the generator instead of copying it\n *\n * The rules exist so that ADDING `@ApiType(...)` (and `@WpMcpTool`) to a contract that passes them\n * always works. That is only true if \"expressible\" has exactly ONE definition, so this scan runs the\n * generator's own code — `ApiDocExtractor` / `TypeResolver` from `@webpieces/api-doc-model` for the\n * OpenAPI half, and `McpSchemaRenderer` tool-by-tool for the MCP half. A second implementation of\n * \"what can be published\" would drift from the first on the release that improved either one, and\n * the drift would be silent: the rules would stay green while generation started failing. The two\n * packages ship on the same release train, so the dependency is in lockstep by construction.\n *\n * Two things here are STRICTER than the generator, deliberately, and both are publishing rules\n * rather than expressibility ones (being stricter cannot break the acceptance contract — it can only\n * refuse something that would have generated):\n *\n * - an `unknown` VALUE TYPE anywhere (`Record<string, unknown>`, `unknown[]`, a bare `unknown`\n * field). The extractor maps it to a primitive and the generator publishes `{}`, which in JSON\n * Schema means \"anything\" — a partner-facing field with no shape, which is the defect the\n * unmapped guard exists for, arriving through a door the guard does not watch.\n * - an RPC whose response is `void`. Fire-and-forget is the CONTRACT of a `cloudtasks` or `cron`\n * endpoint and is allowed there; an RPC that answers nothing can never gain a field without a\n * breaking change, where a named empty response object grows additively forever.\n *\n * ## Why it lives in the rules engine and not in the doc parser\n *\n * `@webpieces/api-doc-model` is only ever pointed at contracts somebody chose to publish. `@ApiType`\n * is a PUBLISHING decision added later, on purpose — so a shape rule that only ran on contracts which\n * had already opted in would let a team discover, six months afterwards, that the type was never\n * expressible, by which time it is in partners' generated clients. Every check below runs on every\n * contract in the workspace.\n *\n * ## Root-level unions are NOT re-checked here\n *\n * `no-root-union-api-type` (#1009) already refuses them, workspace-wide, with its own config key and\n * its own per-site hatch. One implementation. The MCP half still reports one when it meets it,\n * because `McpSchemaRenderer` refuses it as its own backstop and this scan reports whatever the\n * renderer says — which is the correct division: the OpenAPI document publishes a root union\n * perfectly well, and only a tool schema cannot carry one.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n ApiDocExtractionError,\n ApiDocExtractor,\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { matchesAnyGlob } from '@webpieces/rules-config';\nimport { ProjectInfo } from '../project-info';\nimport { collectTsFiles, isTestFile } from './api-ast';\nimport {\n ApiContractDefect,\n ApiDocRule,\n ApiDocRulesFindings,\n ApiRuleFindings,\n DisableComment,\n McpExclusion,\n MCP_RULE,\n OPENAPI_RULE,\n} from './api-doc-rules';\nimport {\n ContractLines,\n RPC_KIND,\n unknownValueCure,\n VOID_RPC_CURE,\n carriesUnknown,\n classifyUnmapped,\n contractLinesOf,\n contractNamesIn,\n isVoidLike,\n toolFailures,\n} from './api-doc-rules-verdicts';\n\n/** `@ApiPath(` at COLUMN ZERO — a docstring that TALKS about a contract declares none. */\nconst DECLARES_CONTRACT = /^@ApiPath\\(/m;\n\n/** ONE contract file, and which of the two rules apply to the project that owns it. */\nclass ContractFile {\n constructor(\n public readonly absPath: string,\n public readonly openApi: boolean,\n public readonly mcp: boolean,\n ) {}\n}\n\n/** Where one declaration sits, already split out of the extractor's `File.ts:LINE:COL` spelling. */\nclass Site {\n constructor(\n public readonly absPath: string,\n public readonly line: number,\n ) {}\n\n /** `path/to/File.ts:LINE`, workspace-relative — what a refusal prints. */\n relativeTo(workspaceRoot: string): string {\n return `${path.relative(workspaceRoot, this.absPath)}:${this.line}`;\n }\n\n /** `abs/File.ts:12:5` -> a Site. An unparseable one falls back to line 1 of `fallback`. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static parse(location: string, fallback: string): Site {\n const match = location.match(/^(.*):(\\d+):\\d+$/);\n if (match === null) return new Site(fallback, 1);\n return new Site(match[1], Number(match[2]));\n }\n}\n\n/**\n * ONE rule's accumulator. It is what applies the per-site disable, so the disable semantics live in\n * exactly one place and cannot differ between the two rules.\n */\nclass DefectCollector {\n private readonly violations: ApiContractDefect[] = [];\n private readonly reasonless: ApiContractDefect[] = [];\n\n constructor(private readonly ruleName: string) {}\n\n /** The rule this collector reports under — what a shared defect's cure has to name. */\n rule(): string {\n return this.ruleName;\n }\n\n add(defect: ApiContractDefect, site: Site): void {\n const disable = DisableComment.readAt(site.absPath, site.line, this.ruleName);\n if (disable === undefined) {\n this.violations.push(defect);\n return;\n }\n if (!disable.hasReason) this.reasonless.push(defect);\n }\n\n findings(): ApiRuleFindings {\n return new ApiRuleFindings(\n DefectCollector.externalFirst(this.violations),\n DefectCollector.externalFirst(this.reasonless),\n );\n }\n\n /**\n * Partner-facing contracts first. The same defect is a different size depending on who reads the\n * document it would have been in, and a list that buries the `external-customer` ones among\n * thirty internal ones has hidden the only urgent line in it.\n */\n // webpieces-disable no-function-outside-class -- private static ordering of this class\n private static externalFirst(found: readonly ApiContractDefect[]): ApiContractDefect[] {\n return [...found].sort(\n (a: ApiContractDefect, b: ApiContractDefect) =>\n Number(b.isExternal()) - Number(a.isExternal()),\n );\n }\n}\n\n/**\n * Routes a defect to the rule that owns it.\n *\n * Every OpenAPI-level defect ALSO blocks MCP, so it is reported by whichever rule is running —\n * `api-rules-for-openapi` when that one is on, and `api-rules-for-mcp` alone when it is not. It is\n * never reported twice: a team running both would otherwise read every shared defect in two places\n * and have to work out that they are one.\n */\nclass DefectSink {\n constructor(\n private readonly openApi: DefectCollector | undefined,\n private readonly mcp: DefectCollector | undefined,\n ) {}\n\n /**\n * A defect that blocks the OpenAPI document, and therefore every tool on it too.\n *\n * The defect is BUILT from the rule that ends up reporting it, not handed in ready-made, because\n * a shared defect does not know in advance which rule will carry it: `api-rules-for-openapi` when\n * that one runs, and `api-rules-for-mcp` alone when it does not. A cure that named a fixed rule\n * would, on the mcp-only configuration, prescribe a `// webpieces-disable` line the collector\n * reading that site does not look for.\n */\n shared(build: (rule: string) => ApiContractDefect, site: Site): void {\n const target = this.openApi ?? this.mcp;\n if (target === undefined) return;\n target.add(build(target.rule()), site);\n }\n\n /** A defect that blocks ONE tool and nothing else. */\n mcpOnly(defect: ApiContractDefect, site: Site): void {\n this.mcp?.add(defect, site);\n }\n\n anyRuleRuns(): boolean {\n return this.openApi !== undefined || this.mcp !== undefined;\n }\n\n /** True when `api-rules-for-mcp` applies to this file — what the exclusion list is scoped to. */\n mcpRuns(): boolean {\n return this.mcp !== undefined;\n }\n}\n\n/**\n * Walks every project's `src`, extracts every `@ApiPath` contract with the generator's own\n * extractor, and judges the result against the two rules.\n *\n * ONE `ts.Program` over every contract file in the workspace, because a DTO a contract reaches\n * routinely lives in another project and the checker has to be able to follow the import — the same\n * reason the repo sweep in `@webpieces/api-doc-model`'s own spec builds one program rather than one\n * per file.\n */\nexport class ApiDocRulesScan {\n /**\n * Every `@InvalidEndpointForMcp` endpoint met on a file the MCP rule applies to.\n *\n * Collected even on a run with no findings at all, because restating them IS the feature: the\n * alternative considered in #1014 was a one-off warning when somebody adds one, and a warning\n * printed once at the moment of the decision is read by the one person who already knows.\n */\n private readonly exclusions: McpExclusion[] = [];\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** OFF unless a caller read otherwise out of webpieces.config.json — see `defaultRules`. */\n private readonly openApiRule: ApiDocRule = ApiDocRule.off(OPENAPI_RULE),\n private readonly mcpRule: ApiDocRule = ApiDocRule.off(MCP_RULE),\n ) {}\n\n run(): ApiDocRulesFindings {\n if (!this.openApiRule.enabled && !this.mcpRule.enabled) return ApiDocRulesFindings.empty();\n const files = this.contractFiles();\n if (files.length === 0) return ApiDocRulesFindings.empty();\n\n const openApi = this.openApiRule.enabled ? new DefectCollector(OPENAPI_RULE) : undefined;\n const mcp = this.mcpRule.enabled ? new DefectCollector(MCP_RULE) : undefined;\n const program = ts.createProgram(\n files.map((file: ContractFile) => file.absPath),\n this.compilerOptions(),\n );\n const toolNames = new Map<string, string>();\n for (const file of files) {\n const sink = new DefectSink(\n file.openApi ? openApi : undefined,\n file.mcp ? mcp : undefined,\n );\n this.judgeFile(file, program, sink, toolNames);\n }\n return new ApiDocRulesFindings(\n openApi?.findings() ?? new ApiRuleFindings([], []),\n mcp?.findings() ?? new ApiRuleFindings([], []),\n this.exclusions,\n );\n }\n\n /** Every contract in one file, or the ONE refusal that stopped the file being read at all. */\n private judgeFile(\n file: ContractFile,\n program: ts.Program,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n if (!sink.anyRuleRuns()) return;\n const source = program.getSourceFile(file.absPath);\n if (source === undefined) return;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an extraction refusal IS a finding; it is reported, not propagated\n try {\n for (const model of new ApiDocExtractor().extractAllFrom(program, source)) {\n this.judgeModel(model, source, sink, toolNames);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof ApiDocExtractionError)) throw err;\n this.reportExtractionFailure(err, file, source, sink);\n }\n }\n\n /**\n * An extraction that REFUSED. Reported under the shared list because it stops BOTH documents:\n * `@Endpoint` arguments that cannot be constant-folded, a bound on a non-numeric field, and the\n * `@ApiType(..., MCP)` ⇔ `@WpMcpTool` biconditional all fail here, before a model exists.\n */\n private reportExtractionFailure(\n error: ApiDocExtractionError,\n file: ContractFile,\n source: ts.SourceFile,\n sink: DefectSink,\n ): void {\n const site = Site.parse(error.location, file.absPath);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n contractNamesIn(source)[0] ?? path.basename(file.absPath),\n '',\n `the contract cannot be read at all — ${error.message}`,\n site.relativeTo(this.workspaceRoot),\n error.cure,\n [],\n ),\n site,\n );\n }\n\n /** ONE contract: the shared OpenAPI checks, then the MCP-only ones. */\n private judgeModel(\n model: ApiDocModel,\n source: ts.SourceFile,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const lines = contractLinesOf(source, model.contractName);\n this.judgeUnmapped(model, sink, source.fileName);\n this.judgeUnknownValues(model, sink, source.fileName);\n this.judgeRpcResponses(model, source, lines, sink);\n this.judgeTools(model, source, lines, sink, toolNames);\n }\n\n /** Everything `TypeResolver` could not represent — the generator's OWN verdict, re-worded. */\n private judgeUnmapped(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const unmapped of model.unmapped) {\n const site = Site.parse(unmapped.location, fallback);\n const verdict = classifyUnmapped(unmapped);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n '',\n verdict.what,\n site.relativeTo(this.workspaceRoot),\n verdict.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** `Record<string, unknown>`, `unknown[]`, `x: unknown` — by VALUE TYPE, never by spelling. */\n private judgeUnknownValues(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const type of model.types.values()) {\n for (const field of type.fields) {\n if (!carriesUnknown(field.type)) continue;\n const site = Site.parse(field.location, fallback);\n sink.shared(\n (rule: string): ApiContractDefect =>\n new ApiContractDefect(\n model.contractName,\n '',\n `'${type.name}.${field.name}' publishes an 'unknown' value, so the ` +\n 'document states no shape for it at all',\n site.relativeTo(this.workspaceRoot),\n unknownValueCure(rule),\n model.apiTypes,\n ),\n site,\n );\n }\n }\n }\n\n /**\n * An RPC must NAME a response DTO, even an empty one.\n *\n * `Promise<void>` on a `cloudtasks` or `cron` endpoint is the CONTRACT — fire-and-forget, nothing\n * to shape — and is allowed. On an RPC it is a one-way door: a `void` response can never gain a\n * field without breaking every generated client, where `{}` grows additively forever. This is a\n * contract-EVOLUTION rule, which is why it is here and not in the MCP half: it is worth having on\n * an RPC that never becomes a tool.\n */\n private judgeRpcResponses(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n ): void {\n for (const endpoint of model.endpoints) {\n if (endpoint.kind !== RPC_KIND) continue;\n if (endpoint.response !== undefined && !isVoidLike(endpoint.response)) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n 'an RPC returns nothing a document can name (void, unknown, or no declared ' +\n 'return type)',\n site.relativeTo(this.workspaceRoot),\n VOID_RPC_CURE,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Everything `McpToolRegistry` refuses to boot on, plus whatever the renderer cannot render. */\n private judgeTools(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const renderer = new McpSchemaRenderer(model);\n for (const endpoint of model.endpoints) {\n if (endpoint.invalidForMcp !== undefined) {\n if (!sink.mcpRuns()) continue;\n // @InvalidEndpointForMcp IS the answer to \"could this be a tool\". The decorator\n // carries the argument, so the rule asks nothing further and no webpieces-disable is\n // needed — a suppression would be a second, weaker spelling of the same declaration.\n this.exclusions.push(\n new McpExclusion(\n model.contractName,\n endpoint.methodName,\n endpoint.invalidForMcp,\n new Site(\n source.fileName,\n lines.lineOf(endpoint.methodName),\n ).relativeTo(this.workspaceRoot),\n ),\n );\n continue;\n }\n if (endpoint.mcpTool === undefined) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n for (const failure of toolFailures(endpoint, renderer, toolNames, model)) {\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n failure.what,\n site.relativeTo(this.workspaceRoot),\n failure.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n for (const methodName of lines.toolsWithoutEndpoint) {\n const site = new Site(source.fileName, lines.lineOf(methodName));\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n methodName,\n 'carries @WpMcpTool but is not an @Endpoint, so it is not routed at all',\n site.relativeTo(this.workspaceRoot),\n \"Add @Endpoint(POST, '/path', READ, RPC) to it, or drop the @WpMcpTool.\",\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Every non-test `.ts` under a project's `src` whose text DECLARES an `@ApiPath` contract. */\n private contractFiles(): ContractFile[] {\n const found: ContractFile[] = [];\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n const openApi =\n this.openApiRule.enabled &&\n !matchesAnyGlob(info.root, this.openApiRule.allowedPaths);\n const mcp =\n this.mcpRule.enabled && !matchesAnyGlob(info.root, this.mcpRule.allowedPaths);\n if (!openApi && !mcp) continue;\n const srcDir = path.join(path.resolve(this.workspaceRoot, info.root), 'src');\n if (!fs.existsSync(srcDir)) continue;\n for (const file of collectTsFiles(srcDir)) {\n if (isTestFile(file)) continue; // a fixture is not a published contract\n if (!DECLARES_CONTRACT.test(fs.readFileSync(file, 'utf8'))) continue;\n found.push(new ContractFile(file, openApi, mcp));\n }\n }\n return found.sort((a: ContractFile, b: ContractFile) => a.absPath.localeCompare(b.absPath));\n }\n\n /**\n * `tsconfig.base.json`'s options when the workspace has one, so an `@webpieces/*` import in a\n * contract RESOLVES and the checker can follow a DTO into another project. Without that the\n * resolver reports every cross-project type as unmapped, which would be a rule failing on its\n * own inability to read rather than on anything the author wrote.\n */\n private compilerOptions(): ts.CompilerOptions {\n const base = path.join(this.workspaceRoot, 'tsconfig.base.json');\n const declared = fs.existsSync(base)\n ? ts.parseJsonConfigFileContent(\n ts.readConfigFile(base, ts.sys.readFile).config,\n ts.sys,\n this.workspaceRoot,\n ).options\n : {};\n return {\n ...declared,\n noEmit: true,\n skipLibCheck: true,\n types: [],\n experimentalDecorators: true,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-doc-rules-scan.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-scan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `api-rules-for-openapi` and `api-rules-for-mcp` (#1011) — the CI half of \"is this contract\n * publishable\", on every `@ApiPath` class IN SCOPE, `@ApiType` or not.\n *\n * \"In scope\" is the rule's `mode` (#1017): `AFFECTED_PROJECT` scans the contracts of the projects the\n * diff touched — the granularity nx already builds at, and the mode a consumer normally picks —\n * while `RUN_EVERY_TIME` scans the whole workspace for a migration sweep. `ApiDocRule.coversProject`\n * is the one place that answers it, for both rules.\n *\n * ## The acceptance contract, and why this file drives the generator instead of copying it\n *\n * The rules exist so that ADDING `@ApiType(...)` (and `@WpMcpTool`) to a contract that passes them\n * always works. That is only true if \"expressible\" has exactly ONE definition, so this scan runs the\n * generator's own code — `ApiDocExtractor` / `TypeResolver` from `@webpieces/api-doc-model` for the\n * OpenAPI half, and `McpSchemaRenderer` tool-by-tool for the MCP half. A second implementation of\n * \"what can be published\" would drift from the first on the release that improved either one, and\n * the drift would be silent: the rules would stay green while generation started failing. The two\n * packages ship on the same release train, so the dependency is in lockstep by construction.\n *\n * Two things here are STRICTER than the generator, deliberately, and both are publishing rules\n * rather than expressibility ones (being stricter cannot break the acceptance contract — it can only\n * refuse something that would have generated):\n *\n * - an `unknown` VALUE TYPE anywhere (`Record<string, unknown>`, `unknown[]`, a bare `unknown`\n * field). The extractor maps it to a primitive and the generator publishes `{}`, which in JSON\n * Schema means \"anything\" — a partner-facing field with no shape, which is the defect the\n * unmapped guard exists for, arriving through a door the guard does not watch.\n * - an `rpc` or `external` endpoint whose response is `void`. Fire-and-forget is the CONTRACT of a\n * `cloudtasks` or `cron` endpoint and is allowed there; an endpoint somebody WAITS on that answers\n * nothing can never gain a field without a breaking change, where a named empty response object\n * grows additively forever (#1017 — #1016 read this narrowly as rpc-only because `external` was\n * unstated).\n *\n * ## Why it lives in the rules engine and not in the doc parser\n *\n * `@webpieces/api-doc-model` is only ever pointed at contracts somebody chose to publish. `@ApiType`\n * is a PUBLISHING decision added later, on purpose — so a shape rule that only ran on contracts which\n * had already opted in would let a team discover, six months afterwards, that the type was never\n * expressible, by which time it is in partners' generated clients. So every check below runs on every\n * contract in scope, opted in or not — `@ApiType` narrows nothing here.\n *\n * ## Root-level unions are NOT re-checked here\n *\n * `no-root-union-api-type` (#1009) already refuses them, workspace-wide, with its own config key and\n * its own per-site hatch. One implementation. The MCP half still reports one when it meets it,\n * because `McpSchemaRenderer` refuses it as its own backstop and this scan reports whatever the\n * renderer says — which is the correct division: the OpenAPI document publishes a root union\n * perfectly well, and only a tool schema cannot carry one.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n ApiDocExtractionError,\n ApiDocExtractor,\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { ProjectInfo } from '../project-info';\nimport { collectTsFiles, isTestFile } from './api-ast';\nimport {\n ApiContractDefect,\n ApiDocRule,\n ApiDocRulesFindings,\n ApiRuleFindings,\n DisableComment,\n McpExclusion,\n MCP_RULE,\n OPENAPI_RULE,\n} from './api-doc-rules';\nimport {\n ANSWERING_KINDS,\n ContractLines,\n unknownValueCure,\n VOID_RPC_CURE,\n carriesUnknown,\n classifyUnmapped,\n contractLinesOf,\n contractNamesIn,\n isVoidLike,\n toolFailures,\n} from './api-doc-rules-verdicts';\n\n/** `@ApiPath(` at COLUMN ZERO — a docstring that TALKS about a contract declares none. */\nconst DECLARES_CONTRACT = /^@ApiPath\\(/m;\n\n/** ONE contract file, and which of the two rules apply to the project that owns it. */\nclass ContractFile {\n constructor(\n public readonly absPath: string,\n public readonly openApi: boolean,\n public readonly mcp: boolean,\n ) {}\n}\n\n/** Where one declaration sits, already split out of the extractor's `File.ts:LINE:COL` spelling. */\nclass Site {\n constructor(\n public readonly absPath: string,\n public readonly line: number,\n ) {}\n\n /** `path/to/File.ts:LINE`, workspace-relative — what a refusal prints. */\n relativeTo(workspaceRoot: string): string {\n return `${path.relative(workspaceRoot, this.absPath)}:${this.line}`;\n }\n\n /** `abs/File.ts:12:5` -> a Site. An unparseable one falls back to line 1 of `fallback`. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static parse(location: string, fallback: string): Site {\n const match = location.match(/^(.*):(\\d+):\\d+$/);\n if (match === null) return new Site(fallback, 1);\n return new Site(match[1], Number(match[2]));\n }\n}\n\n/**\n * ONE rule's accumulator. It is what applies the per-site disable, so the disable semantics live in\n * exactly one place and cannot differ between the two rules.\n */\nclass DefectCollector {\n private readonly violations: ApiContractDefect[] = [];\n private readonly reasonless: ApiContractDefect[] = [];\n\n constructor(private readonly ruleName: string) {}\n\n /** The rule this collector reports under — what a shared defect's cure has to name. */\n rule(): string {\n return this.ruleName;\n }\n\n add(defect: ApiContractDefect, site: Site): void {\n const disable = DisableComment.readAt(site.absPath, site.line, this.ruleName);\n if (disable === undefined) {\n this.violations.push(defect);\n return;\n }\n if (!disable.hasReason) this.reasonless.push(defect);\n }\n\n findings(): ApiRuleFindings {\n return new ApiRuleFindings(\n DefectCollector.externalFirst(this.violations),\n DefectCollector.externalFirst(this.reasonless),\n );\n }\n\n /**\n * Partner-facing contracts first. The same defect is a different size depending on who reads the\n * document it would have been in, and a list that buries the `external-customer` ones among\n * thirty internal ones has hidden the only urgent line in it.\n */\n // webpieces-disable no-function-outside-class -- private static ordering of this class\n private static externalFirst(found: readonly ApiContractDefect[]): ApiContractDefect[] {\n return [...found].sort(\n (a: ApiContractDefect, b: ApiContractDefect) =>\n Number(b.isExternal()) - Number(a.isExternal()),\n );\n }\n}\n\n/**\n * Routes a defect to the rule that owns it.\n *\n * Every OpenAPI-level defect ALSO blocks MCP, so it is reported by whichever rule is running —\n * `api-rules-for-openapi` when that one is on, and `api-rules-for-mcp` alone when it is not. It is\n * never reported twice: a team running both would otherwise read every shared defect in two places\n * and have to work out that they are one.\n */\nclass DefectSink {\n constructor(\n private readonly openApi: DefectCollector | undefined,\n private readonly mcp: DefectCollector | undefined,\n ) {}\n\n /**\n * A defect that blocks the OpenAPI document, and therefore every tool on it too.\n *\n * The defect is BUILT from the rule that ends up reporting it, not handed in ready-made, because\n * a shared defect does not know in advance which rule will carry it: `api-rules-for-openapi` when\n * that one runs, and `api-rules-for-mcp` alone when it does not. A cure that named a fixed rule\n * would, on the mcp-only configuration, prescribe a `// webpieces-disable` line the collector\n * reading that site does not look for.\n */\n shared(build: (rule: string) => ApiContractDefect, site: Site): void {\n const target = this.openApi ?? this.mcp;\n if (target === undefined) return;\n target.add(build(target.rule()), site);\n }\n\n /** A defect that blocks ONE tool and nothing else. */\n mcpOnly(defect: ApiContractDefect, site: Site): void {\n this.mcp?.add(defect, site);\n }\n\n anyRuleRuns(): boolean {\n return this.openApi !== undefined || this.mcp !== undefined;\n }\n\n /** True when `api-rules-for-mcp` applies to this file — what the exclusion list is scoped to. */\n mcpRuns(): boolean {\n return this.mcp !== undefined;\n }\n}\n\n/**\n * Walks every project's `src`, extracts every `@ApiPath` contract with the generator's own\n * extractor, and judges the result against the two rules.\n *\n * ONE `ts.Program` over every contract file in the workspace, because a DTO a contract reaches\n * routinely lives in another project and the checker has to be able to follow the import — the same\n * reason the repo sweep in `@webpieces/api-doc-model`'s own spec builds one program rather than one\n * per file.\n */\nexport class ApiDocRulesScan {\n /**\n * Every `@InvalidEndpointForMcp` endpoint met on a file the MCP rule applies to.\n *\n * Collected even on a run with no findings at all, because restating them IS the feature: the\n * alternative considered in #1014 was a one-off warning when somebody adds one, and a warning\n * printed once at the moment of the decision is read by the one person who already knows.\n */\n private readonly exclusions: McpExclusion[] = [];\n\n constructor(\n private readonly workspaceRoot: string,\n private readonly projectInfos: Map<string, ProjectInfo>,\n /** OFF unless a caller read otherwise out of webpieces.config.json, which MUST state it. */\n private readonly openApiRule: ApiDocRule = ApiDocRule.off(OPENAPI_RULE),\n private readonly mcpRule: ApiDocRule = ApiDocRule.off(MCP_RULE),\n ) {}\n\n run(): ApiDocRulesFindings {\n if (!this.openApiRule.enabled && !this.mcpRule.enabled) return ApiDocRulesFindings.empty();\n const files = this.contractFiles();\n if (files.length === 0) return ApiDocRulesFindings.empty();\n\n const openApi = this.openApiRule.enabled ? new DefectCollector(OPENAPI_RULE) : undefined;\n const mcp = this.mcpRule.enabled ? new DefectCollector(MCP_RULE) : undefined;\n const program = ts.createProgram(\n files.map((file: ContractFile) => file.absPath),\n this.compilerOptions(),\n );\n const toolNames = new Map<string, string>();\n for (const file of files) {\n const sink = new DefectSink(\n file.openApi ? openApi : undefined,\n file.mcp ? mcp : undefined,\n );\n this.judgeFile(file, program, sink, toolNames);\n }\n return new ApiDocRulesFindings(\n openApi?.findings() ?? new ApiRuleFindings([], []),\n mcp?.findings() ?? new ApiRuleFindings([], []),\n this.exclusions,\n );\n }\n\n /** Every contract in one file, or the ONE refusal that stopped the file being read at all. */\n private judgeFile(\n file: ContractFile,\n program: ts.Program,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n if (!sink.anyRuleRuns()) return;\n const source = program.getSourceFile(file.absPath);\n if (source === undefined) return;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an extraction refusal IS a finding; it is reported, not propagated\n try {\n for (const model of new ApiDocExtractor().extractAllFrom(program, source)) {\n this.judgeModel(model, source, sink, toolNames);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof ApiDocExtractionError)) throw err;\n this.reportExtractionFailure(err, file, source, sink);\n }\n }\n\n /**\n * An extraction that REFUSED. Reported under the shared list because it stops BOTH documents:\n * `@Endpoint` arguments that cannot be constant-folded, a bound on a non-numeric field, and the\n * `@ApiType(..., MCP)` ⇔ `@WpMcpTool` biconditional all fail here, before a model exists.\n */\n private reportExtractionFailure(\n error: ApiDocExtractionError,\n file: ContractFile,\n source: ts.SourceFile,\n sink: DefectSink,\n ): void {\n const site = Site.parse(error.location, file.absPath);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n contractNamesIn(source)[0] ?? path.basename(file.absPath),\n '',\n `the contract cannot be read at all — ${error.message}`,\n site.relativeTo(this.workspaceRoot),\n error.cure,\n [],\n ),\n site,\n );\n }\n\n /** ONE contract: the shared OpenAPI checks, then the MCP-only ones. */\n private judgeModel(\n model: ApiDocModel,\n source: ts.SourceFile,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const lines = contractLinesOf(source, model.contractName);\n this.judgeUnmapped(model, sink, source.fileName);\n this.judgeUnknownValues(model, sink, source.fileName);\n this.judgeAnsweringResponses(model, source, lines, sink);\n this.judgeTools(model, source, lines, sink, toolNames);\n }\n\n /** Everything `TypeResolver` could not represent — the generator's OWN verdict, re-worded. */\n private judgeUnmapped(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const unmapped of model.unmapped) {\n const site = Site.parse(unmapped.location, fallback);\n const verdict = classifyUnmapped(unmapped);\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n '',\n verdict.what,\n site.relativeTo(this.workspaceRoot),\n verdict.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** `Record<string, unknown>`, `unknown[]`, `x: unknown` — by VALUE TYPE, never by spelling. */\n private judgeUnknownValues(model: ApiDocModel, sink: DefectSink, fallback: string): void {\n for (const type of model.types.values()) {\n for (const field of type.fields) {\n if (!carriesUnknown(field.type)) continue;\n const site = Site.parse(field.location, fallback);\n sink.shared(\n (rule: string): ApiContractDefect =>\n new ApiContractDefect(\n model.contractName,\n '',\n `'${type.name}.${field.name}' publishes an 'unknown' value, so the ` +\n 'document states no shape for it at all',\n site.relativeTo(this.workspaceRoot),\n unknownValueCure(rule),\n model.apiTypes,\n ),\n site,\n );\n }\n }\n }\n\n /**\n * An endpoint somebody WAITS ON must NAME a response DTO, even an empty one.\n *\n * `Promise<void>` on a `cloudtasks` or `cron` endpoint is the CONTRACT — fire-and-forget, nothing\n * to shape — and is allowed. On an `rpc` OR an `external` it is a one-way door: both are\n * synchronous request/response, an outside caller reads what comes back, and a `void` response\n * can never gain a field without breaking every generated client where `{}` grows additively\n * forever. This is a contract-EVOLUTION rule, which is why it is here and not in the MCP half: it\n * is worth having on an endpoint that never becomes a tool. See ANSWERING_KINDS (#1017).\n */\n private judgeAnsweringResponses(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n ): void {\n for (const endpoint of model.endpoints) {\n // `.some` and not `.includes`: the model's `kind` is a widened string, and the list is\n // typed EndpointKind so a kind that stops existing is a compile error here.\n if (!ANSWERING_KINDS.some((kind: string): boolean => kind === endpoint.kind)) continue;\n if (endpoint.response !== undefined && !isVoidLike(endpoint.response)) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n sink.shared(\n (): ApiContractDefect => new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n `an ${endpoint.kind} endpoint returns nothing a document can name (void, ` +\n 'unknown, or no declared return type)',\n site.relativeTo(this.workspaceRoot),\n VOID_RPC_CURE,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Everything `McpToolRegistry` refuses to boot on, plus whatever the renderer cannot render. */\n private judgeTools(\n model: ApiDocModel,\n source: ts.SourceFile,\n lines: ContractLines,\n sink: DefectSink,\n toolNames: Map<string, string>,\n ): void {\n const renderer = new McpSchemaRenderer(model);\n for (const endpoint of model.endpoints) {\n if (endpoint.invalidForMcp !== undefined) {\n if (!sink.mcpRuns()) continue;\n // @InvalidEndpointForMcp IS the answer to \"could this be a tool\". The decorator\n // carries the argument, so the rule asks nothing further and no webpieces-disable is\n // needed — a suppression would be a second, weaker spelling of the same declaration.\n this.exclusions.push(\n new McpExclusion(\n model.contractName,\n endpoint.methodName,\n endpoint.invalidForMcp,\n new Site(\n source.fileName,\n lines.lineOf(endpoint.methodName),\n ).relativeTo(this.workspaceRoot),\n ),\n );\n continue;\n }\n if (endpoint.mcpTool === undefined) continue;\n const site = new Site(source.fileName, lines.lineOf(endpoint.methodName));\n for (const failure of toolFailures(endpoint, renderer, toolNames, model)) {\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n endpoint.methodName,\n failure.what,\n site.relativeTo(this.workspaceRoot),\n failure.cure,\n model.apiTypes,\n ),\n site,\n );\n }\n }\n for (const methodName of lines.toolsWithoutEndpoint) {\n const site = new Site(source.fileName, lines.lineOf(methodName));\n sink.mcpOnly(\n new ApiContractDefect(\n model.contractName,\n methodName,\n 'carries @WpMcpTool but is not an @Endpoint, so it is not routed at all',\n site.relativeTo(this.workspaceRoot),\n \"Add @Endpoint(POST, '/path', READ, RPC) to it, or drop the @WpMcpTool.\",\n model.apiTypes,\n ),\n site,\n );\n }\n }\n\n /** Every non-test `.ts` under a project's `src` whose text DECLARES an `@ApiPath` contract. */\n private contractFiles(): ContractFile[] {\n const found: ContractFile[] = [];\n for (const info of this.projectInfos.values()) {\n if (info.root === '' || info.root === '.') continue;\n // ONE question, asked of the rule itself: `mode` (OFF / AFFECTED_PROJECT /\n // RUN_EVERY_TIME) and `allowedPaths` are both folded into coversProject, so the\n // affected-project narrowing cannot be honoured in one branch and skipped in the other.\n const openApi = this.openApiRule.coversProject(info.root);\n const mcp = this.mcpRule.coversProject(info.root);\n if (!openApi && !mcp) continue;\n const srcDir = path.join(path.resolve(this.workspaceRoot, info.root), 'src');\n if (!fs.existsSync(srcDir)) continue;\n for (const file of collectTsFiles(srcDir)) {\n if (isTestFile(file)) continue; // a fixture is not a published contract\n if (!DECLARES_CONTRACT.test(fs.readFileSync(file, 'utf8'))) continue;\n found.push(new ContractFile(file, openApi, mcp));\n }\n }\n return found.sort((a: ContractFile, b: ContractFile) => a.absPath.localeCompare(b.absPath));\n }\n\n /**\n * `tsconfig.base.json`'s options when the workspace has one, so an `@webpieces/*` import in a\n * contract RESOLVES and the checker can follow a DTO into another project. Without that the\n * resolver reports every cross-project type as unmapped, which would be a rule failing on its\n * own inability to read rather than on anything the author wrote.\n */\n private compilerOptions(): ts.CompilerOptions {\n const base = path.join(this.workspaceRoot, 'tsconfig.base.json');\n const declared = fs.existsSync(base)\n ? ts.parseJsonConfigFileContent(\n ts.readConfigFile(base, ts.sys.readFile).config,\n ts.sys,\n this.workspaceRoot,\n ).options\n : {};\n return {\n ...declared,\n noEmit: true,\n skipLibCheck: true,\n types: [],\n experimentalDecorators: true,\n };\n }\n}\n"]}
|
|
@@ -18,6 +18,19 @@ import { EndpointKind } from './api-relations';
|
|
|
18
18
|
* set the literal belongs to is pinned by that type.
|
|
19
19
|
*/
|
|
20
20
|
export declare const RPC_KIND: EndpointKind;
|
|
21
|
+
/**
|
|
22
|
+
* The endpoint kinds whose response a caller WAITS FOR, and which therefore must name a DTO (#1017).
|
|
23
|
+
*
|
|
24
|
+
* `rpc` and `external` are both synchronous request/response: somebody posts and reads what comes
|
|
25
|
+
* back, so there IS a body and it must be a named type that can gain a field later. `void` on either
|
|
26
|
+
* is a one-way door — a `void` response can never grow without breaking every generated client, where
|
|
27
|
+
* an empty DTO grows additively forever. #1016 refused it on `rpc` only, because `external` was
|
|
28
|
+
* unstated at the time; it is stated now.
|
|
29
|
+
*
|
|
30
|
+
* `cloudtasks` and `cron` are deliberately NOT here: delivery is fire-and-forget, nobody waits for a
|
|
31
|
+
* body, and `Promise<void>` is the CONTRACT rather than an omission.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ANSWERING_KINDS: readonly EndpointKind[];
|
|
21
34
|
/** ONE reason a declaration is refused, in the two halves every refusal prints. */
|
|
22
35
|
export declare class Verdict {
|
|
23
36
|
readonly what: string;
|
|
@@ -46,7 +59,7 @@ export declare class ContractLines {
|
|
|
46
59
|
* failure mode a printed cure must not have.
|
|
47
60
|
*/
|
|
48
61
|
export declare function unknownValueCure(rule: string): string;
|
|
49
|
-
/** The one cure for a `void`
|
|
62
|
+
/** The one cure for a `void` endpoint that somebody is WAITING on (rpc or external). */
|
|
50
63
|
export declare const VOID_RPC_CURE: string;
|
|
51
64
|
/**
|
|
52
65
|
* Why the resolver could not map this type, said in the author's vocabulary.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* form" cannot give an author.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.VOID_RPC_CURE = exports.ContractLines = exports.Verdict = exports.RPC_KIND = void 0;
|
|
14
|
+
exports.VOID_RPC_CURE = exports.ContractLines = exports.Verdict = exports.ANSWERING_KINDS = exports.RPC_KIND = void 0;
|
|
15
15
|
exports.unknownValueCure = unknownValueCure;
|
|
16
16
|
exports.classifyUnmapped = classifyUnmapped;
|
|
17
17
|
exports.carriesUnknown = carriesUnknown;
|
|
@@ -28,6 +28,19 @@ const api_doc_model_1 = require("@webpieces/api-doc-model");
|
|
|
28
28
|
* set the literal belongs to is pinned by that type.
|
|
29
29
|
*/
|
|
30
30
|
exports.RPC_KIND = 'rpc';
|
|
31
|
+
/**
|
|
32
|
+
* The endpoint kinds whose response a caller WAITS FOR, and which therefore must name a DTO (#1017).
|
|
33
|
+
*
|
|
34
|
+
* `rpc` and `external` are both synchronous request/response: somebody posts and reads what comes
|
|
35
|
+
* back, so there IS a body and it must be a named type that can gain a field later. `void` on either
|
|
36
|
+
* is a one-way door — a `void` response can never grow without breaking every generated client, where
|
|
37
|
+
* an empty DTO grows additively forever. #1016 refused it on `rpc` only, because `external` was
|
|
38
|
+
* unstated at the time; it is stated now.
|
|
39
|
+
*
|
|
40
|
+
* `cloudtasks` and `cron` are deliberately NOT here: delivery is fire-and-forget, nobody waits for a
|
|
41
|
+
* body, and `Promise<void>` is the CONTRACT rather than an omission.
|
|
42
|
+
*/
|
|
43
|
+
exports.ANSWERING_KINDS = ['rpc', 'external'];
|
|
31
44
|
/** The scalar keywords a MIXED SCALAR union is made of. */
|
|
32
45
|
const SCALAR_KEYWORDS = new Set([
|
|
33
46
|
ts.SyntaxKind.StringKeyword,
|
|
@@ -80,11 +93,13 @@ function unknownValueCure(rule) {
|
|
|
80
93
|
'An existing no-any-unknown disable does NOT count — that rule asked whether the code is ' +
|
|
81
94
|
'type-safe, which is a different question from whether a partner is handed a shapeless field.');
|
|
82
95
|
}
|
|
83
|
-
/** The one cure for a `void`
|
|
96
|
+
/** The one cure for a `void` endpoint that somebody is WAITING on (rpc or external). */
|
|
84
97
|
exports.VOID_RPC_CURE = 'Declare a named response DTO and return Promise<That>, even when it has no fields today: an ' +
|
|
85
98
|
'empty object grows additively forever, and a void response cannot gain a field without ' +
|
|
86
|
-
'breaking every generated client.
|
|
87
|
-
'
|
|
99
|
+
'breaking every generated client. An external endpoint is a synchronous call an outside caller ' +
|
|
100
|
+
'waits on, so it has a body too — an empty one serializes as {} and the failure detail rides on ' +
|
|
101
|
+
'the thrown error. Promise<void> stays legal on a cloudtasks or cron endpoint, where ' +
|
|
102
|
+
'fire-and-forget is the contract.';
|
|
88
103
|
/**
|
|
89
104
|
* Why the resolver could not map this type, said in the author's vocabulary.
|
|
90
105
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-doc-rules-verdicts.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pure VERDICTS the api-contract rules print, split out of `api-doc-rules-scan.ts`, which owns\n * the walk and is at its file-size limit.\n *\n * Nothing here touches the filesystem or the project graph. Each function answers one question about\n * ONE declaration the extractor or the renderer has already ruled on, and turns it into the two\n * halves every webpieces refusal carries — what is wrong, and what to do instead. The DECISION is\n * never made here: `model.unmapped` is the extractor's and a render failure is the renderer's. What\n * this file adds is the WORDING, which is the part a generic \"no model representation for this type\n * form\" cannot give an author.\n */\n\nimport * as ts from 'typescript';\nimport {\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { EndpointKind } from './api-relations';\n\n/**\n * The one trigger kind an MCP tool may have. A LITERAL, for the same reason `EndpointKind` beside it\n * is one: this package deliberately does not depend on `@webpieces/core-util` at runtime, and the\n * set the literal belongs to is pinned by that type.\n */\nexport const RPC_KIND: EndpointKind = 'rpc';\n\n/** The scalar keywords a MIXED SCALAR union is made of. */\nconst SCALAR_KEYWORDS = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.StringKeyword,\n ts.SyntaxKind.NumberKeyword,\n ts.SyntaxKind.BooleanKeyword,\n]);\n\n/** ONE reason a declaration is refused, in the two halves every refusal prints. */\nexport class Verdict {\n constructor(\n public readonly what: string,\n public readonly cure: string,\n ) {}\n}\n\n\n/** Method name -> the line its declaration starts on, for one contract class. */\nexport class ContractLines {\n constructor(\n public readonly classLine: number,\n public readonly byMethod: ReadonlyMap<string, number>,\n /** Methods carrying `@WpMcpTool` that do NOT also carry `@Endpoint`. */\n public readonly toolsWithoutEndpoint: readonly string[],\n ) {}\n\n lineOf(methodName: string): number {\n return this.byMethod.get(methodName) ?? this.classLine;\n }\n}\n\n\n/**\n * The cure for an `unknown` value, for whichever rule is REPORTING it.\n *\n * It takes the rule name rather than naming one, because this defect is shared: it blocks the\n * OpenAPI document, so `DefectSink` routes it to `api-rules-for-openapi` when that rule is running\n * and to `api-rules-for-mcp` when it is the only one. A hard-coded rule name would then hand an\n * author, on the mcp-only configuration, a `// webpieces-disable api-rules-for-openapi` line that\n * the collector reading the site does not look for — a cure that does not cure, which is the one\n * failure mode a printed cure must not have.\n */\n// webpieces-disable no-function-outside-class -- pure message builder, beside the verdicts that print it\nexport function unknownValueCure(rule: string): string {\n return (\n 'Name the value type — a DTO, an array of one, a string-literal union, or ' +\n 'Record<string, ThatDto>. If the shape really is unknowable (a transport envelope, ' +\n 'arbitrary SQL rows), write the per-site disable and say so: ' +\n `// webpieces-disable ${rule} -- <why this field is published with no shape>. ` +\n 'An existing no-any-unknown disable does NOT count — that rule asked whether the code is ' +\n 'type-safe, which is a different question from whether a partner is handed a shapeless field.'\n );\n}\n\n/** The one cure for a `void` RPC. */\nexport const VOID_RPC_CURE =\n 'Declare a named response DTO and return Promise<That>, even when it has no fields today: an ' +\n 'empty object grows additively forever, and a void response cannot gain a field without ' +\n 'breaking every generated client. Promise<void> stays legal on a cloudtasks or cron endpoint, ' +\n 'where fire-and-forget is the contract.';\n\n/**\n * Why the resolver could not map this type, said in the author's vocabulary.\n *\n * The DECISION to refuse is the extractor's and is not second-guessed here — every entry in\n * `model.unmapped` becomes a defect. What this adds is the MESSAGE: an open enum and a mixed scalar\n * union are the two shapes a real repo actually hits (measured: 6 of 98 endpoints, from exactly\n * these two), and \"no model representation for this type form\" tells their author nothing.\n */\n// webpieces-disable no-function-outside-class -- pure classifier over the extractor's own verdict\nexport function classifyUnmapped(unmapped: UnmappedType): Verdict {\n const branches = unionBranchesOf(unmapped.typeText);\n if (isOpenEnum(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is an OPEN ENUM — literals unioned with the wide type`,\n 'Use an enum OR a string, not both. TypeScript COLLAPSES this union to the wide type, ' +\n 'so the literals buy no compile-time safety and a document cannot state them as a ' +\n \"closed set. Either drop the `| string` (`'a' | 'b'` publishes as a real enum), or \" +\n 'drop the literals and list the known values in the JSDoc.',\n );\n }\n if (isMixedScalarUnion(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is a MIXED SCALAR union`,\n 'Give the field ONE type. JSON Schema can spell {\"type\": [\"string\",\"number\"]}, but ' +\n 'this framework\\'s ApiJsonSchema helpers read a type array as \"one real type plus ' +\n 'maybe null\" (baseTypeOf returns the first non-null member), so publishing it ' +\n 'would VALIDATE WRONGLY — which is worse than refusing it. Pick the type, or wrap ' +\n 'the alternatives in a discriminated union of named objects.',\n );\n }\n return new Verdict(\n `'${unmapped.typeText}' has no shape a document can state — ${unmapped.reason}`,\n 'Give it a type a document can carry: a named DTO, an array of one, a string-literal ' +\n 'union, a Record<string, ThatDto>, or a primitive. A field with no shape publishes as ' +\n '\"anything\".',\n );\n}\n\n/** The branches of `typeText` when it parses as a union, else an empty list. */\n// webpieces-disable no-function-outside-class -- pure parser used by classifyUnmapped alone\nfunction unionBranchesOf(typeText: string): readonly ts.TypeNode[] {\n const parsed = ts.createSourceFile(\n '__unmapped.ts',\n `type __X = ${typeText};`,\n ts.ScriptTarget.Latest,\n true,\n );\n const alias = parsed.statements[0];\n if (alias === undefined || !ts.isTypeAliasDeclaration(alias)) return [];\n if (!ts.isUnionTypeNode(alias.type)) return [];\n return alias.type.types.filter((branch: ts.TypeNode) => !isNullishBranch(branch));\n}\n\n/** `'a' | 'b' | string`, and the numeric equivalent. See #1010 and the decision on #1011. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isOpenEnum(branches: readonly ts.TypeNode[]): boolean {\n const wide = branches.some(\n (branch: ts.TypeNode) =>\n branch.kind === ts.SyntaxKind.StringKeyword ||\n branch.kind === ts.SyntaxKind.NumberKeyword,\n );\n const literals = branches.some((branch: ts.TypeNode) => ts.isLiteralTypeNode(branch));\n return wide && literals;\n}\n\n/** `string | number`, `string | boolean | null` — two or more DIFFERENT scalar keywords. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isMixedScalarUnion(branches: readonly ts.TypeNode[]): boolean {\n if (branches.length < 2) return false;\n const kinds = new Set<ts.SyntaxKind>();\n for (const branch of branches) {\n if (!SCALAR_KEYWORDS.has(branch.kind)) return false;\n kinds.add(branch.kind);\n }\n return kinds.size >= 2;\n}\n\n/** `null` / `undefined` branches — nullability, not composition, exactly as the resolver reads it. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isNullishBranch(node: ts.TypeNode): boolean {\n if (node.kind === ts.SyntaxKind.UndefinedKeyword || node.kind === ts.SyntaxKind.NullKeyword) {\n return true;\n }\n return ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword;\n}\n\n/**\n * True when this type PUBLISHES an `unknown` value.\n *\n * It looks at the VALUE TYPE wherever it sits — the item of an array, the value of an open map, the\n * field itself — and never at the `Record<>` spelling, because the real cases in the wild are not\n * all Records: `params?: unknown[]` is the same defect written differently.\n *\n * It does NOT descend through a `ref`: a named DTO is its own entry in the model and its own fields\n * are judged there, so descending would report the same field once per type that points at it.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function carriesUnknown(ref: TypeRef): boolean {\n if (ref.kind === 'primitive') return ref.primitive === 'unknown';\n if (ref.kind === 'array') return carriesUnknown(ref.items!);\n if (ref.kind === 'openMap') return carriesUnknown(ref.values!);\n return false;\n}\n\n/**\n * `void`, `unknown` and `any` all reach the model as the same primitive — the resolver maps the\n * three keywords onto one, because to a document they say the identical thing: nothing.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function isVoidLike(ref: TypeRef): boolean {\n return ref.kind === 'primitive' && ref.primitive === 'unknown';\n}\n\n/**\n * Every reason this tool could not be served, in the order `McpToolRegistry` asks them at boot —\n * unique name, trigger kind, HTTP auth, MCP auth — and then the renderer's own verdict.\n *\n * The registry's four are restated from the model rather than driven, because they are decorator\n * PRESENCE facts the model already carries verbatim; the SCHEMA question is the one with a real\n * implementation behind it, and that one is driven.\n */\n// webpieces-disable no-function-outside-class, max-lines-new-methods -- pure judge over one endpoint, and the four boot checks read as one list\nexport function toolFailures(\n endpoint: DocumentedEndpoint,\n renderer: McpSchemaRenderer,\n toolNames: Map<string, string>,\n model: ApiDocModel,\n): readonly Verdict[] {\n const found: Verdict[] = [];\n const toolName = endpoint.mcpTool!.name;\n const claimed = toolNames.get(toolName);\n if (claimed === undefined) {\n toolNames.set(toolName, `${model.contractName}.${endpoint.methodName}`);\n } else {\n found.push(\n new Verdict(\n `tool name '${toolName}' is already declared by ${claimed}`,\n 'Tool names are the protocol identity and are globally unique — rename one of the ' +\n 'two @WpMcpTool declarations.',\n ),\n );\n }\n if (endpoint.kind !== RPC_KIND) {\n found.push(\n new Verdict(\n `an MCP tool is declared on a '${endpoint.kind}' endpoint`,\n 'Only an RPC endpoint can be a tool — a cloudtasks, cron or external endpoint is ' +\n 'driven by something other than a caller. Make it RPC, or drop the @WpMcpTool.',\n ),\n );\n }\n if (endpoint.auth === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool declares no HTTP auth',\n 'Put a @WpAuth... decorator on the method. The MCP server refuses to boot without ' +\n 'one, because a tool with no credential is an unauthenticated endpoint an ' +\n 'agent can call.',\n ),\n );\n }\n if (endpoint.mcpAuthText === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool does not declare @WpMcpAuthJwt(...)',\n 'Add @WpMcpAuthJwt(...) beside the HTTP auth. MCP authorization is rechecked ' +\n 'before the endpoint boundary and is declared separately on purpose.',\n ),\n );\n }\n const rendered = renderFailure(renderer, endpoint);\n if (rendered !== undefined) found.push(rendered);\n return found;\n}\n\n/**\n * The renderer's own refusal for one tool, or undefined when it renders.\n *\n * This is the whole acceptance contract for the MCP half: \"passes the rule\" and \"renders a tool\" are\n * the SAME statement, because the rule asks the renderer. It covers the method's missing JSDoc,\n * every reachable DTO field's missing JSDoc, a request or response that is not a named object, a\n * root-level union, a recursive DTO, an `unknown` leaf and a malformed `@mcpHeader` — none of which\n * is restated here.\n *\n * One verdict per tool, because the renderer stops at the first thing it cannot draw. A tool blocked\n * by several undocumented fields is therefore fixed a round at a time, which is the price of having\n * exactly one definition of renderable rather than a second walk that could disagree with it.\n */\n// webpieces-disable no-function-outside-class -- pure judge beside toolFailures\nfunction renderFailure(\n renderer: McpSchemaRenderer,\n endpoint: DocumentedEndpoint,\n): Verdict | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the render refusal IS the verdict\n try {\n renderer.tool(endpoint);\n return undefined;\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof McpRenderError)) throw err;\n return new Verdict(`${err.message} — no MCP schema could be built`, err.cure);\n }\n}\n\n/** Every `@ApiPath` class name declared at the top level of a file, in declaration order. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nexport function contractNamesIn(source: ts.SourceFile): string[] {\n const names: string[] = [];\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name === undefined) continue;\n if (hasDecoratorNamed(statement, 'ApiPath')) names.push(statement.name.text);\n }\n return names;\n}\n\n/**\n * Where each method of one contract starts, and which of them carry `@WpMcpTool` without\n * `@Endpoint`.\n *\n * The line numbers exist because the MCP failures come from the RENDERER, whose location is a NAME\n * (`Order.window`) rather than a file offset — it publishes a schema, not a diagnostic. Anchoring\n * every tool failure at its METHOD is also where an author would write the disable: \"this tool is\n * deliberately not renderable\" is a decision about the method, not about a field three DTOs down.\n */\n// webpieces-disable no-function-outside-class -- pure AST reader beside contractNamesIn\nexport function contractLinesOf(source: ts.SourceFile, contractName: string): ContractLines {\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name?.text !== contractName) continue;\n const byMethod = new Map<string, number>();\n const orphanTools: string[] = [];\n for (const member of statement.members) {\n if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) continue;\n byMethod.set(member.name.text, lineOf(source, member));\n if (hasDecoratorNamed(member, 'WpMcpTool') && !hasDecoratorNamed(member, 'Endpoint')) {\n orphanTools.push(member.name.text);\n }\n }\n return new ContractLines(lineOf(source, statement), byMethod, orphanTools);\n }\n return new ContractLines(1, new Map<string, number>(), []);\n}\n\n/** 1-based line of a node's first token, decorators excluded. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction lineOf(source: ts.SourceFile, node: ts.Node): number {\n return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;\n}\n\n/** True when `node` carries `@name(...)`. Matched on the syntax, like every reader in this file. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction hasDecoratorNamed(node: ts.Node, name: string): boolean {\n const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (\n ts.isCallExpression(call) &&\n ts.isIdentifier(call.expression) &&\n call.expression.text === name\n ) {\n return true;\n }\n }\n return false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api-doc-rules-verdicts.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/nx-webpieces-rules/src/lib/api-usage/api-doc-rules-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The pure VERDICTS the api-contract rules print, split out of `api-doc-rules-scan.ts`, which owns\n * the walk and is at its file-size limit.\n *\n * Nothing here touches the filesystem or the project graph. Each function answers one question about\n * ONE declaration the extractor or the renderer has already ruled on, and turns it into the two\n * halves every webpieces refusal carries — what is wrong, and what to do instead. The DECISION is\n * never made here: `model.unmapped` is the extractor's and a render failure is the renderer's. What\n * this file adds is the WORDING, which is the part a generic \"no model representation for this type\n * form\" cannot give an author.\n */\n\nimport * as ts from 'typescript';\nimport {\n ApiDocModel,\n DocumentedEndpoint,\n McpRenderError,\n McpSchemaRenderer,\n TypeRef,\n UnmappedType,\n} from '@webpieces/api-doc-model';\nimport { EndpointKind } from './api-relations';\n\n/**\n * The one trigger kind an MCP tool may have. A LITERAL, for the same reason `EndpointKind` beside it\n * is one: this package deliberately does not depend on `@webpieces/core-util` at runtime, and the\n * set the literal belongs to is pinned by that type.\n */\nexport const RPC_KIND: EndpointKind = 'rpc';\n\n/**\n * The endpoint kinds whose response a caller WAITS FOR, and which therefore must name a DTO (#1017).\n *\n * `rpc` and `external` are both synchronous request/response: somebody posts and reads what comes\n * back, so there IS a body and it must be a named type that can gain a field later. `void` on either\n * is a one-way door — a `void` response can never grow without breaking every generated client, where\n * an empty DTO grows additively forever. #1016 refused it on `rpc` only, because `external` was\n * unstated at the time; it is stated now.\n *\n * `cloudtasks` and `cron` are deliberately NOT here: delivery is fire-and-forget, nobody waits for a\n * body, and `Promise<void>` is the CONTRACT rather than an omission.\n */\nexport const ANSWERING_KINDS: readonly EndpointKind[] = ['rpc', 'external'];\n\n/** The scalar keywords a MIXED SCALAR union is made of. */\nconst SCALAR_KEYWORDS = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.StringKeyword,\n ts.SyntaxKind.NumberKeyword,\n ts.SyntaxKind.BooleanKeyword,\n]);\n\n/** ONE reason a declaration is refused, in the two halves every refusal prints. */\nexport class Verdict {\n constructor(\n public readonly what: string,\n public readonly cure: string,\n ) {}\n}\n\n\n/** Method name -> the line its declaration starts on, for one contract class. */\nexport class ContractLines {\n constructor(\n public readonly classLine: number,\n public readonly byMethod: ReadonlyMap<string, number>,\n /** Methods carrying `@WpMcpTool` that do NOT also carry `@Endpoint`. */\n public readonly toolsWithoutEndpoint: readonly string[],\n ) {}\n\n lineOf(methodName: string): number {\n return this.byMethod.get(methodName) ?? this.classLine;\n }\n}\n\n\n/**\n * The cure for an `unknown` value, for whichever rule is REPORTING it.\n *\n * It takes the rule name rather than naming one, because this defect is shared: it blocks the\n * OpenAPI document, so `DefectSink` routes it to `api-rules-for-openapi` when that rule is running\n * and to `api-rules-for-mcp` when it is the only one. A hard-coded rule name would then hand an\n * author, on the mcp-only configuration, a `// webpieces-disable api-rules-for-openapi` line that\n * the collector reading the site does not look for — a cure that does not cure, which is the one\n * failure mode a printed cure must not have.\n */\n// webpieces-disable no-function-outside-class -- pure message builder, beside the verdicts that print it\nexport function unknownValueCure(rule: string): string {\n return (\n 'Name the value type — a DTO, an array of one, a string-literal union, or ' +\n 'Record<string, ThatDto>. If the shape really is unknowable (a transport envelope, ' +\n 'arbitrary SQL rows), write the per-site disable and say so: ' +\n `// webpieces-disable ${rule} -- <why this field is published with no shape>. ` +\n 'An existing no-any-unknown disable does NOT count — that rule asked whether the code is ' +\n 'type-safe, which is a different question from whether a partner is handed a shapeless field.'\n );\n}\n\n/** The one cure for a `void` endpoint that somebody is WAITING on (rpc or external). */\nexport const VOID_RPC_CURE =\n 'Declare a named response DTO and return Promise<That>, even when it has no fields today: an ' +\n 'empty object grows additively forever, and a void response cannot gain a field without ' +\n 'breaking every generated client. An external endpoint is a synchronous call an outside caller ' +\n 'waits on, so it has a body too — an empty one serializes as {} and the failure detail rides on ' +\n 'the thrown error. Promise<void> stays legal on a cloudtasks or cron endpoint, where ' +\n 'fire-and-forget is the contract.';\n\n/**\n * Why the resolver could not map this type, said in the author's vocabulary.\n *\n * The DECISION to refuse is the extractor's and is not second-guessed here — every entry in\n * `model.unmapped` becomes a defect. What this adds is the MESSAGE: an open enum and a mixed scalar\n * union are the two shapes a real repo actually hits (measured: 6 of 98 endpoints, from exactly\n * these two), and \"no model representation for this type form\" tells their author nothing.\n */\n// webpieces-disable no-function-outside-class -- pure classifier over the extractor's own verdict\nexport function classifyUnmapped(unmapped: UnmappedType): Verdict {\n const branches = unionBranchesOf(unmapped.typeText);\n if (isOpenEnum(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is an OPEN ENUM — literals unioned with the wide type`,\n 'Use an enum OR a string, not both. TypeScript COLLAPSES this union to the wide type, ' +\n 'so the literals buy no compile-time safety and a document cannot state them as a ' +\n \"closed set. Either drop the `| string` (`'a' | 'b'` publishes as a real enum), or \" +\n 'drop the literals and list the known values in the JSDoc.',\n );\n }\n if (isMixedScalarUnion(branches)) {\n return new Verdict(\n `'${unmapped.typeText}' is a MIXED SCALAR union`,\n 'Give the field ONE type. JSON Schema can spell {\"type\": [\"string\",\"number\"]}, but ' +\n 'this framework\\'s ApiJsonSchema helpers read a type array as \"one real type plus ' +\n 'maybe null\" (baseTypeOf returns the first non-null member), so publishing it ' +\n 'would VALIDATE WRONGLY — which is worse than refusing it. Pick the type, or wrap ' +\n 'the alternatives in a discriminated union of named objects.',\n );\n }\n return new Verdict(\n `'${unmapped.typeText}' has no shape a document can state — ${unmapped.reason}`,\n 'Give it a type a document can carry: a named DTO, an array of one, a string-literal ' +\n 'union, a Record<string, ThatDto>, or a primitive. A field with no shape publishes as ' +\n '\"anything\".',\n );\n}\n\n/** The branches of `typeText` when it parses as a union, else an empty list. */\n// webpieces-disable no-function-outside-class -- pure parser used by classifyUnmapped alone\nfunction unionBranchesOf(typeText: string): readonly ts.TypeNode[] {\n const parsed = ts.createSourceFile(\n '__unmapped.ts',\n `type __X = ${typeText};`,\n ts.ScriptTarget.Latest,\n true,\n );\n const alias = parsed.statements[0];\n if (alias === undefined || !ts.isTypeAliasDeclaration(alias)) return [];\n if (!ts.isUnionTypeNode(alias.type)) return [];\n return alias.type.types.filter((branch: ts.TypeNode) => !isNullishBranch(branch));\n}\n\n/** `'a' | 'b' | string`, and the numeric equivalent. See #1010 and the decision on #1011. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isOpenEnum(branches: readonly ts.TypeNode[]): boolean {\n const wide = branches.some(\n (branch: ts.TypeNode) =>\n branch.kind === ts.SyntaxKind.StringKeyword ||\n branch.kind === ts.SyntaxKind.NumberKeyword,\n );\n const literals = branches.some((branch: ts.TypeNode) => ts.isLiteralTypeNode(branch));\n return wide && literals;\n}\n\n/** `string | number`, `string | boolean | null` — two or more DIFFERENT scalar keywords. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isMixedScalarUnion(branches: readonly ts.TypeNode[]): boolean {\n if (branches.length < 2) return false;\n const kinds = new Set<ts.SyntaxKind>();\n for (const branch of branches) {\n if (!SCALAR_KEYWORDS.has(branch.kind)) return false;\n kinds.add(branch.kind);\n }\n return kinds.size >= 2;\n}\n\n/** `null` / `undefined` branches — nullability, not composition, exactly as the resolver reads it. */\n// webpieces-disable no-function-outside-class -- pure predicate beside classifyUnmapped\nfunction isNullishBranch(node: ts.TypeNode): boolean {\n if (node.kind === ts.SyntaxKind.UndefinedKeyword || node.kind === ts.SyntaxKind.NullKeyword) {\n return true;\n }\n return ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword;\n}\n\n/**\n * True when this type PUBLISHES an `unknown` value.\n *\n * It looks at the VALUE TYPE wherever it sits — the item of an array, the value of an open map, the\n * field itself — and never at the `Record<>` spelling, because the real cases in the wild are not\n * all Records: `params?: unknown[]` is the same defect written differently.\n *\n * It does NOT descend through a `ref`: a named DTO is its own entry in the model and its own fields\n * are judged there, so descending would report the same field once per type that points at it.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function carriesUnknown(ref: TypeRef): boolean {\n if (ref.kind === 'primitive') return ref.primitive === 'unknown';\n if (ref.kind === 'array') return carriesUnknown(ref.items!);\n if (ref.kind === 'openMap') return carriesUnknown(ref.values!);\n return false;\n}\n\n/**\n * `void`, `unknown` and `any` all reach the model as the same primitive — the resolver maps the\n * three keywords onto one, because to a document they say the identical thing: nothing.\n */\n// webpieces-disable no-function-outside-class -- pure predicate over one TypeRef\nexport function isVoidLike(ref: TypeRef): boolean {\n return ref.kind === 'primitive' && ref.primitive === 'unknown';\n}\n\n/**\n * Every reason this tool could not be served, in the order `McpToolRegistry` asks them at boot —\n * unique name, trigger kind, HTTP auth, MCP auth — and then the renderer's own verdict.\n *\n * The registry's four are restated from the model rather than driven, because they are decorator\n * PRESENCE facts the model already carries verbatim; the SCHEMA question is the one with a real\n * implementation behind it, and that one is driven.\n */\n// webpieces-disable no-function-outside-class, max-lines-new-methods -- pure judge over one endpoint, and the four boot checks read as one list\nexport function toolFailures(\n endpoint: DocumentedEndpoint,\n renderer: McpSchemaRenderer,\n toolNames: Map<string, string>,\n model: ApiDocModel,\n): readonly Verdict[] {\n const found: Verdict[] = [];\n const toolName = endpoint.mcpTool!.name;\n const claimed = toolNames.get(toolName);\n if (claimed === undefined) {\n toolNames.set(toolName, `${model.contractName}.${endpoint.methodName}`);\n } else {\n found.push(\n new Verdict(\n `tool name '${toolName}' is already declared by ${claimed}`,\n 'Tool names are the protocol identity and are globally unique — rename one of the ' +\n 'two @WpMcpTool declarations.',\n ),\n );\n }\n if (endpoint.kind !== RPC_KIND) {\n found.push(\n new Verdict(\n `an MCP tool is declared on a '${endpoint.kind}' endpoint`,\n 'Only an RPC endpoint can be a tool — a cloudtasks, cron or external endpoint is ' +\n 'driven by something other than a caller. Make it RPC, or drop the @WpMcpTool.',\n ),\n );\n }\n if (endpoint.auth === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool declares no HTTP auth',\n 'Put a @WpAuth... decorator on the method. The MCP server refuses to boot without ' +\n 'one, because a tool with no credential is an unauthenticated endpoint an ' +\n 'agent can call.',\n ),\n );\n }\n if (endpoint.mcpAuthText === undefined) {\n found.push(\n new Verdict(\n 'an MCP tool does not declare @WpMcpAuthJwt(...)',\n 'Add @WpMcpAuthJwt(...) beside the HTTP auth. MCP authorization is rechecked ' +\n 'before the endpoint boundary and is declared separately on purpose.',\n ),\n );\n }\n const rendered = renderFailure(renderer, endpoint);\n if (rendered !== undefined) found.push(rendered);\n return found;\n}\n\n/**\n * The renderer's own refusal for one tool, or undefined when it renders.\n *\n * This is the whole acceptance contract for the MCP half: \"passes the rule\" and \"renders a tool\" are\n * the SAME statement, because the rule asks the renderer. It covers the method's missing JSDoc,\n * every reachable DTO field's missing JSDoc, a request or response that is not a named object, a\n * root-level union, a recursive DTO, an `unknown` leaf and a malformed `@mcpHeader` — none of which\n * is restated here.\n *\n * One verdict per tool, because the renderer stops at the first thing it cannot draw. A tool blocked\n * by several undocumented fields is therefore fixed a round at a time, which is the price of having\n * exactly one definition of renderable rather than a second walk that could disagree with it.\n */\n// webpieces-disable no-function-outside-class -- pure judge beside toolFailures\nfunction renderFailure(\n renderer: McpSchemaRenderer,\n endpoint: DocumentedEndpoint,\n): Verdict | undefined {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- the render refusal IS the verdict\n try {\n renderer.tool(endpoint);\n return undefined;\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof McpRenderError)) throw err;\n return new Verdict(`${err.message} — no MCP schema could be built`, err.cure);\n }\n}\n\n/** Every `@ApiPath` class name declared at the top level of a file, in declaration order. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nexport function contractNamesIn(source: ts.SourceFile): string[] {\n const names: string[] = [];\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name === undefined) continue;\n if (hasDecoratorNamed(statement, 'ApiPath')) names.push(statement.name.text);\n }\n return names;\n}\n\n/**\n * Where each method of one contract starts, and which of them carry `@WpMcpTool` without\n * `@Endpoint`.\n *\n * The line numbers exist because the MCP failures come from the RENDERER, whose location is a NAME\n * (`Order.window`) rather than a file offset — it publishes a schema, not a diagnostic. Anchoring\n * every tool failure at its METHOD is also where an author would write the disable: \"this tool is\n * deliberately not renderable\" is a decision about the method, not about a field three DTOs down.\n */\n// webpieces-disable no-function-outside-class -- pure AST reader beside contractNamesIn\nexport function contractLinesOf(source: ts.SourceFile, contractName: string): ContractLines {\n for (const statement of source.statements) {\n if (!ts.isClassDeclaration(statement) || statement.name?.text !== contractName) continue;\n const byMethod = new Map<string, number>();\n const orphanTools: string[] = [];\n for (const member of statement.members) {\n if (!ts.isMethodDeclaration(member) || !ts.isIdentifier(member.name)) continue;\n byMethod.set(member.name.text, lineOf(source, member));\n if (hasDecoratorNamed(member, 'WpMcpTool') && !hasDecoratorNamed(member, 'Endpoint')) {\n orphanTools.push(member.name.text);\n }\n }\n return new ContractLines(lineOf(source, statement), byMethod, orphanTools);\n }\n return new ContractLines(1, new Map<string, number>(), []);\n}\n\n/** 1-based line of a node's first token, decorators excluded. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction lineOf(source: ts.SourceFile, node: ts.Node): number {\n return source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;\n}\n\n/** True when `node` carries `@name(...)`. Matched on the syntax, like every reader in this file. */\n// webpieces-disable no-function-outside-class -- pure AST reader\nfunction hasDecoratorNamed(node: ts.Node, name: string): boolean {\n const decorators = ts.canHaveDecorators(node) ? (ts.getDecorators(node) ?? []) : [];\n for (const decorator of decorators) {\n const call = decorator.expression;\n if (\n ts.isCallExpression(call) &&\n ts.isIdentifier(call.expression) &&\n call.expression.text === name\n ) {\n return true;\n }\n }\n return false;\n}\n"]}
|
|
@@ -99,26 +99,63 @@ export declare class ApiDocRulesFindings {
|
|
|
99
99
|
mcpExclusions?: readonly McpExclusion[]);
|
|
100
100
|
static empty(): ApiDocRulesFindings;
|
|
101
101
|
}
|
|
102
|
+
/** The mode value that narrows the scan to the projects the diff touched. */
|
|
103
|
+
export declare const AFFECTED_PROJECT_MODE = "AFFECTED_PROJECT";
|
|
102
104
|
/**
|
|
103
105
|
* ONE rule's switches, resolved from webpieces.config.json once per scan.
|
|
104
106
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
107
|
+
* There is NO default (#1017). Both rules are schema'd, so `webpieces.config.json` must carry an
|
|
108
|
+
* entry for each or the config FAILS TO LOAD naming them — a consumer states `OFF`,
|
|
109
|
+
* `AFFECTED_PROJECT` or `RUN_EVERY_TIME` out loud. The `off()` a bare constructor or a unit test
|
|
110
|
+
* gets is not a default; it is what a caller that configured nothing asked for.
|
|
108
111
|
*/
|
|
109
112
|
export declare class ApiDocRule {
|
|
110
113
|
readonly name: string;
|
|
111
114
|
readonly enabled: boolean;
|
|
112
115
|
/** Project roots this rule does not apply to — `allowedPaths` in the config. */
|
|
113
116
|
readonly allowedPaths: readonly string[];
|
|
117
|
+
/**
|
|
118
|
+
* Workspace-relative paths the diff touched, under `AFFECTED_PROJECT`; `null` under
|
|
119
|
+
* `RUN_EVERY_TIME`, which means every project is in scope.
|
|
120
|
+
*
|
|
121
|
+
* `null` is also what an UNCOMPUTABLE diff resolves to — no merge-base, a shallow clone, no
|
|
122
|
+
* repository at all. A diff that could not be read is not evidence that nothing changed, and
|
|
123
|
+
* the only safe reading of "I do not know what changed" is "look at all of it".
|
|
124
|
+
*/
|
|
125
|
+
readonly changedPaths: readonly string[] | null;
|
|
114
126
|
constructor(name: string, enabled: boolean,
|
|
115
127
|
/** Project roots this rule does not apply to — `allowedPaths` in the config. */
|
|
116
|
-
allowedPaths: readonly string[]
|
|
117
|
-
/**
|
|
128
|
+
allowedPaths: readonly string[],
|
|
129
|
+
/**
|
|
130
|
+
* Workspace-relative paths the diff touched, under `AFFECTED_PROJECT`; `null` under
|
|
131
|
+
* `RUN_EVERY_TIME`, which means every project is in scope.
|
|
132
|
+
*
|
|
133
|
+
* `null` is also what an UNCOMPUTABLE diff resolves to — no merge-base, a shallow clone, no
|
|
134
|
+
* repository at all. A diff that could not be read is not evidence that nothing changed, and
|
|
135
|
+
* the only safe reading of "I do not know what changed" is "look at all of it".
|
|
136
|
+
*/
|
|
137
|
+
changedPaths?: readonly string[] | null);
|
|
138
|
+
/** ARMED over every project — what a unit test constructs, and what RUN_EVERY_TIME resolves to. */
|
|
118
139
|
static armed(name: string): ApiDocRule;
|
|
140
|
+
/** ARMED over the projects owning one of `changedPaths` — what AFFECTED_PROJECT resolves to. */
|
|
141
|
+
static affected(name: string, changedPaths: readonly string[]): ApiDocRule;
|
|
119
142
|
static off(name: string): ApiDocRule;
|
|
143
|
+
/**
|
|
144
|
+
* True when this rule scans the contracts of the project rooted at `root`.
|
|
145
|
+
*
|
|
146
|
+
* ONE place answers it, for both rules, so `allowedPaths` and the affected-project narrowing can
|
|
147
|
+
* never be applied by one caller and skipped by another.
|
|
148
|
+
*/
|
|
149
|
+
coversProject(root: string): boolean;
|
|
120
150
|
/** `mode: OFF` and the time-box/branch hatches come from RuleGate, so there is ONE reading. */
|
|
121
151
|
static fromConfig(workspaceRoot: string, name: string): ApiDocRule;
|
|
152
|
+
/**
|
|
153
|
+
* Every path the diff touched, against the base nx itself uses (`NX_BASE`, else the merge-base
|
|
154
|
+
* with origin/main). NOT ts-only and INCLUDING deletions: a deleted DTO changes what a project's
|
|
155
|
+
* contracts can express exactly as an added one does, and a `project.json` edit can change which
|
|
156
|
+
* project owns a contract at all.
|
|
157
|
+
*/
|
|
158
|
+
private static changedPathsOf;
|
|
122
159
|
}
|
|
123
160
|
/** ONE `webpieces-disable` naming a rule, and whether it gave a reason. */
|
|
124
161
|
export declare class DisableComment {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* config is read exactly once per executor run.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.DisableComment = exports.ApiDocRule = exports.ApiDocRulesFindings = exports.McpExclusion = exports.ApiRuleFindings = exports.ApiContractDefect = exports.EXTERNAL_CUSTOMER_API_TYPE = exports.MCP_RULE = exports.OPENAPI_RULE = void 0;
|
|
10
|
+
exports.DisableComment = exports.ApiDocRule = exports.AFFECTED_PROJECT_MODE = exports.ApiDocRulesFindings = exports.McpExclusion = exports.ApiRuleFindings = exports.ApiContractDefect = exports.EXTERNAL_CUSTOMER_API_TYPE = exports.MCP_RULE = exports.OPENAPI_RULE = void 0;
|
|
11
11
|
const tslib_1 = require("tslib");
|
|
12
12
|
const fs = tslib_1.__importStar(require("fs"));
|
|
13
13
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
@@ -131,32 +131,67 @@ class ApiDocRulesFindings {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
exports.ApiDocRulesFindings = ApiDocRulesFindings;
|
|
134
|
+
/** The mode value that narrows the scan to the projects the diff touched. */
|
|
135
|
+
exports.AFFECTED_PROJECT_MODE = 'AFFECTED_PROJECT';
|
|
134
136
|
/**
|
|
135
137
|
* ONE rule's switches, resolved from webpieces.config.json once per scan.
|
|
136
138
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
139
|
+
* There is NO default (#1017). Both rules are schema'd, so `webpieces.config.json` must carry an
|
|
140
|
+
* entry for each or the config FAILS TO LOAD naming them — a consumer states `OFF`,
|
|
141
|
+
* `AFFECTED_PROJECT` or `RUN_EVERY_TIME` out loud. The `off()` a bare constructor or a unit test
|
|
142
|
+
* gets is not a default; it is what a caller that configured nothing asked for.
|
|
140
143
|
*/
|
|
141
144
|
class ApiDocRule {
|
|
142
145
|
name;
|
|
143
146
|
enabled;
|
|
144
147
|
allowedPaths;
|
|
148
|
+
changedPaths;
|
|
145
149
|
constructor(name, enabled,
|
|
146
150
|
/** Project roots this rule does not apply to — `allowedPaths` in the config. */
|
|
147
|
-
allowedPaths
|
|
151
|
+
allowedPaths,
|
|
152
|
+
/**
|
|
153
|
+
* Workspace-relative paths the diff touched, under `AFFECTED_PROJECT`; `null` under
|
|
154
|
+
* `RUN_EVERY_TIME`, which means every project is in scope.
|
|
155
|
+
*
|
|
156
|
+
* `null` is also what an UNCOMPUTABLE diff resolves to — no merge-base, a shallow clone, no
|
|
157
|
+
* repository at all. A diff that could not be read is not evidence that nothing changed, and
|
|
158
|
+
* the only safe reading of "I do not know what changed" is "look at all of it".
|
|
159
|
+
*/
|
|
160
|
+
changedPaths = null) {
|
|
148
161
|
this.name = name;
|
|
149
162
|
this.enabled = enabled;
|
|
150
163
|
this.allowedPaths = allowedPaths;
|
|
164
|
+
this.changedPaths = changedPaths;
|
|
151
165
|
}
|
|
152
|
-
/** ARMED
|
|
166
|
+
/** ARMED over every project — what a unit test constructs, and what RUN_EVERY_TIME resolves to. */
|
|
153
167
|
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
154
168
|
static armed(name) {
|
|
155
|
-
return new ApiDocRule(name, true, []);
|
|
169
|
+
return new ApiDocRule(name, true, [], null);
|
|
170
|
+
}
|
|
171
|
+
/** ARMED over the projects owning one of `changedPaths` — what AFFECTED_PROJECT resolves to. */
|
|
172
|
+
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
173
|
+
static affected(name, changedPaths) {
|
|
174
|
+
return new ApiDocRule(name, true, [], changedPaths);
|
|
156
175
|
}
|
|
157
176
|
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
158
177
|
static off(name) {
|
|
159
|
-
return new ApiDocRule(name, false, []);
|
|
178
|
+
return new ApiDocRule(name, false, [], null);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* True when this rule scans the contracts of the project rooted at `root`.
|
|
182
|
+
*
|
|
183
|
+
* ONE place answers it, for both rules, so `allowedPaths` and the affected-project narrowing can
|
|
184
|
+
* never be applied by one caller and skipped by another.
|
|
185
|
+
*/
|
|
186
|
+
coversProject(root) {
|
|
187
|
+
if (!this.enabled)
|
|
188
|
+
return false;
|
|
189
|
+
if ((0, rules_config_1.matchesAnyGlob)(root, this.allowedPaths))
|
|
190
|
+
return false;
|
|
191
|
+
if (this.changedPaths === null)
|
|
192
|
+
return true;
|
|
193
|
+
const prefix = `${root}/`;
|
|
194
|
+
return this.changedPaths.some((each) => each.startsWith(prefix));
|
|
160
195
|
}
|
|
161
196
|
/** `mode: OFF` and the time-box/branch hatches come from RuleGate, so there is ONE reading. */
|
|
162
197
|
// webpieces-disable no-function-outside-class -- static factory of this class
|
|
@@ -166,7 +201,28 @@ class ApiDocRule {
|
|
|
166
201
|
}
|
|
167
202
|
const rule = (0, rules_config_1.loadAndValidate)(workspaceRoot).resolved.rules.get(name);
|
|
168
203
|
const allowed = rule?.options['allowedPaths'];
|
|
169
|
-
|
|
204
|
+
const allowedPaths = Array.isArray(allowed) ? allowed : [];
|
|
205
|
+
const affected = rule?.options['mode'] === exports.AFFECTED_PROJECT_MODE
|
|
206
|
+
? ApiDocRule.changedPathsOf(workspaceRoot)
|
|
207
|
+
: null;
|
|
208
|
+
return new ApiDocRule(name, true, allowedPaths, affected);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Every path the diff touched, against the base nx itself uses (`NX_BASE`, else the merge-base
|
|
212
|
+
* with origin/main). NOT ts-only and INCLUDING deletions: a deleted DTO changes what a project's
|
|
213
|
+
* contracts can express exactly as an added one does, and a `project.json` edit can change which
|
|
214
|
+
* project owns a contract at all.
|
|
215
|
+
*/
|
|
216
|
+
// webpieces-disable no-function-outside-class -- private static reader of this class
|
|
217
|
+
static changedPathsOf(workspaceRoot) {
|
|
218
|
+
const scope = new rules_config_1.DiffScope();
|
|
219
|
+
const range = scope.resolveBase(workspaceRoot);
|
|
220
|
+
if (range.base === undefined)
|
|
221
|
+
return null;
|
|
222
|
+
const opts = new rules_config_1.ChangedFilesOptions();
|
|
223
|
+
opts.tsOnly = false;
|
|
224
|
+
opts.includeDeletions = true;
|
|
225
|
+
return scope.getChangedFiles(workspaceRoot, range.base, range.head, opts);
|
|
170
226
|
}
|
|
171
227
|
}
|
|
172
228
|
exports.ApiDocRule = ApiDocRule;
|