@sap/eslint-plugin-cds 2.0.4 → 2.2.1
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/CHANGELOG.md +82 -1
- package/README.md +2 -3
- package/lib/api/formatter.js +170 -119
- package/lib/api/index.js +22 -9
- package/lib/impl/constants.js +42 -48
- package/lib/impl/index.js +56 -49
- package/lib/impl/parser.js +37 -25
- package/lib/impl/processor.js +23 -0
- package/lib/impl/ruleFactory.js +319 -117
- package/lib/impl/rules/assoc2many-ambiguous-key.js +153 -0
- package/lib/impl/rules/cds-compile-error.js +35 -0
- package/lib/impl/rules/latest-cds-version.js +41 -34
- package/lib/impl/rules/min-node-version.js +37 -34
- package/lib/impl/rules/no-db-keywords.js +25 -0
- package/lib/impl/rules/require-2many-oncond.js +29 -0
- package/lib/impl/rules/rule.hbs +13 -0
- package/lib/impl/rules/sql-cast-suggestion.js +43 -39
- package/lib/impl/rules/start-elements-lowercase.js +66 -0
- package/lib/impl/rules/start-entities-uppercase.js +56 -0
- package/lib/impl/types.d.ts +48 -0
- package/lib/impl/utils/helpers.js +89 -0
- package/lib/impl/utils/jsonc.js +1 -0
- package/lib/impl/utils/model.js +485 -0
- package/lib/impl/utils/rules.js +541 -0
- package/lib/impl/utils/validate.js +56 -0
- package/package.json +3 -3
- package/lib/impl/rules/assocs-card-flaw.js +0 -215
- package/lib/impl/rules/csn-compile-error.js +0 -37
- package/lib/impl/rules/lower-camelcase-elements.js +0 -42
- package/lib/impl/rules/upper-camelcase-entities.js +0 -50
- package/lib/impl/utils.js +0 -370
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { files } = require("../constants");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
/**
|
|
5
|
+
* Checks whether plugin is used in 'test' mode
|
|
6
|
+
* @returns boolean
|
|
7
|
+
*/
|
|
8
|
+
isTest: function () {
|
|
9
|
+
let isTest = false;
|
|
10
|
+
if (process.argv[1].includes("jest") || process.argv[1].includes("mocha")) {
|
|
11
|
+
isTest = true;
|
|
12
|
+
}
|
|
13
|
+
return isTest;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks whether the given file path contains a file extension allowed by
|
|
18
|
+
* the plugin
|
|
19
|
+
* @param {string} filePath
|
|
20
|
+
* @returns boolean
|
|
21
|
+
*/
|
|
22
|
+
isValidFile: function (filePath) {
|
|
23
|
+
const regex = new RegExp(
|
|
24
|
+
`${files
|
|
25
|
+
.map((file) => {
|
|
26
|
+
return file.replace("*", "");
|
|
27
|
+
})
|
|
28
|
+
.join("$|")}$`
|
|
29
|
+
);
|
|
30
|
+
return regex.test(filePath);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks whether the plugin is run via the VS Code ESLint extension (editor)
|
|
35
|
+
* @returns boolean
|
|
36
|
+
*/
|
|
37
|
+
isEditor() {
|
|
38
|
+
return process.argv.join(" ").includes("dbaeumer.vscode-eslint");
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prints a formatted message string according to the styles provided
|
|
43
|
+
* @param msg message to print
|
|
44
|
+
* @param styles array of styles for apply
|
|
45
|
+
* @returns
|
|
46
|
+
*/
|
|
47
|
+
styleText: function (msg, styles) {
|
|
48
|
+
const types = {
|
|
49
|
+
reset: "\x1b[0m", // Default
|
|
50
|
+
bold: "\x1b[1m", // Bold/Bright
|
|
51
|
+
link: "\x1b[4m", // underline
|
|
52
|
+
red: "\x1b[31m", // Foreground Red
|
|
53
|
+
green: "\x1b[32m", // Foreground Green
|
|
54
|
+
yellow: "\x1b[33m", // Foreground Yellow
|
|
55
|
+
};
|
|
56
|
+
let msgStyle = "";
|
|
57
|
+
styles.forEach((style) => {
|
|
58
|
+
msgStyle += types[style];
|
|
59
|
+
});
|
|
60
|
+
return `${msgStyle}${msg}${types.reset}`;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Checks whether the compiled cds model contains compilation errors which
|
|
65
|
+
* should only be reported via the 'cds-compile-error' rule
|
|
66
|
+
* @param cds cds object
|
|
67
|
+
* @param ruleID rule name
|
|
68
|
+
* @returns
|
|
69
|
+
*/
|
|
70
|
+
hasCompilationError: function (context) {
|
|
71
|
+
const cds = context.cds;
|
|
72
|
+
const ruleID = context.ruleID;
|
|
73
|
+
if (
|
|
74
|
+
cds &&
|
|
75
|
+
cds.model &&
|
|
76
|
+
cds.model.err &&
|
|
77
|
+
cds.model.err.message.startsWith("CDS compilation failed")
|
|
78
|
+
) {
|
|
79
|
+
if (
|
|
80
|
+
ruleID === "@sap/cds/cds-compile-error" ||
|
|
81
|
+
ruleID === "cds-compile-error"
|
|
82
|
+
) {
|
|
83
|
+
cds.model.err;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function peg$subclass(t,r){function e(){this.constructor=t}e.prototype=r.prototype,t.prototype=new e}function peg$SyntaxError(t,r,e,n){this.message=t,this.expected=r,this.found=e,this.location=n,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,peg$SyntaxError)}function peg$parse(t,r){r=void 0!==r?r:{};var e,n={},a={JSON:it},c=it,u=nt("{",!1),o=function(t,r,e){t[r]=e},s=nt("}",!1),h=nt("[",!1),i=function(t,r){t.push(r)},l=nt("]",!1),f=/^[+\-]/,A=at(["+","-"],!1,!1),p=/^[0-9]/,g=at([["0","9"]],!1,!1),d=nt(".",!1),C=nt("e",!1),v=ct("string"),x='"',b=nt('"',!1),y="\\",m=nt("\\",!1),E=nt("/",!1),S=nt("n",!1),$=nt("r",!1),F=nt("t",!1),w=nt("b",!1),R=nt("f",!1),j=nt("u",!1),k=/^[0-9a-f]/,M=at([["0","9"],["a","f"]],!1,!1),N=function(t){return String.fromCharCode(parseInt(t,16))},T=/^[^"]/,I=at(['"'],!0,!1),J="null",O=nt("null",!1),U="true",q=nt("true",!1),z="false",B=nt("false",!1),D=nt(":",!1),G=nt(",",!1),H=/^[ \t\n\r]/,K=at([" ","\t","\n","\r"],!1,!1),L=nt("//",!1),P=/^[\n\r\u2028\u2029]/,Q=at(["\n","\r","\u2028","\u2029"],!1,!1),V={type:"any"},W=nt("/*",!1),X="*/",Y=nt("*/",!1),Z=0,_=[{line:1,column:1}],tt=0,rt=[],et=0;if("startRule"in r){if(!(r.startRule in a))throw new Error("Can't start parsing from rule \""+r.startRule+'".');c=a[r.startRule]}function nt(t,r){return{type:"literal",text:t,ignoreCase:r}}function at(t,r,e){return{type:"class",parts:t,inverted:r,ignoreCase:e}}function ct(t){return{type:"other",description:t}}function ut(r){var e,n=_[r];if(n)return n;for(e=r-1;!_[e];)e--;for(n={line:(n=_[e]).line,column:n.column};e<r;)10===t.charCodeAt(e)?(n.line++,n.column=1):n.column++,e++;return _[r]=n,n}function ot(t,r){var e=ut(t),n=ut(r);return{start:{offset:t,line:e.line,column:e.column},end:{offset:r,line:n.line,column:n.column}}}function st(t){Z<tt||(Z>tt&&(tt=Z,rt=[]),rt.push(t))}function ht(t,r,e){return new peg$SyntaxError(peg$SyntaxError.buildMessage(t,r),t,r,e)}function it(){var t,r;return t=Z,gt()!==n&&(r=lt())!==n&>()!==n?(t,t=r):(Z=t,t=n),t}function lt(){var r;return(r=function(){var r,e,a,c,h,i,l,f,A;r=Z,e=Z,(a=gt())!==n?(123===t.charCodeAt(Z)?(c="{",Z++):(c=n,0===et&&st(u)),c!==n&&(h=gt())!==n?(e,e=a={}):(Z=e,e=n)):(Z=e,e=n);if(e!==n){if(a=Z,c=Z,(h=ft())!==n&&(i=At())!==n&&(l=lt())!==n?(c,c=h=o(e,h,l)):(Z=c,c=n),c!==n){for(h=[],i=Z,(l=pt())!==n&&(f=ft())!==n&&At()!==n&&(A=lt())!==n?(i,i=l=o(e,f,A)):(Z=i,i=n);i!==n;)h.push(i),i=Z,(l=pt())!==n&&(f=ft())!==n&&At()!==n&&(A=lt())!==n?(i,i=l=o(e,f,A)):(Z=i,i=n);h!==n?((i=pt())===n&&(i=null),i!==n?a=c=[c,h,i]:(Z=a,a=n)):(Z=a,a=n)}else Z=a,a=n;a===n&&(a=null),a!==n&&(c=gt())!==n?(125===t.charCodeAt(Z)?(h="}",Z++):(h=n,0===et&&st(s)),h!==n&&(i=gt())!==n?(r,r=e=e):(Z=r,r=n)):(Z=r,r=n)}else Z=r,r=n;return r}())===n&&(r=function(){var r,e,a,c,u,o,s;r=Z,e=Z,(a=gt())!==n?(91===t.charCodeAt(Z)?(c="[",Z++):(c=n,0===et&&st(h)),c!==n&&(u=gt())!==n?(e,e=a=[]):(Z=e,e=n)):(Z=e,e=n);if(e!==n){if(a=Z,c=Z,(u=lt())!==n&&(c,u=i(e,u)),(c=u)!==n){for(u=[],o=Z,pt()!==n&&(s=lt())!==n?(o,o=i(e,s)):(Z=o,o=n);o!==n;)u.push(o),o=Z,pt()!==n&&(s=lt())!==n?(o,o=i(e,s)):(Z=o,o=n);u!==n?((o=pt())===n&&(o=null),o!==n?a=c=[c,u,o]:(Z=a,a=n)):(Z=a,a=n)}else Z=a,a=n;a===n&&(a=null),a!==n&&(c=gt())!==n?(93===t.charCodeAt(Z)?(u="]",Z++):(u=n,0===et&&st(l)),u!==n&&(o=gt())!==n?(r,r=e=e):(Z=r,r=n)):(Z=r,r=n)}else Z=r,r=n;return r}())===n&&(r=function(){var r,e;r=Z,t.substr(Z,4)===J?(e=J,Z+=4):(e=n,0===et&&st(O));e!==n&&(r,e=null);return r=e}())===n&&(r=function(){var r,e;r=Z,t.substr(Z,4)===U?(e=U,Z+=4):(e=n,0===et&&st(q));e!==n&&(r,e=!0);return r=e}())===n&&(r=function(){var r,e;r=Z,t.substr(Z,5)===z?(e=z,Z+=5):(e=n,0===et&&st(B));e!==n&&(r,e=!1);return r=e}())===n&&(r=function(){var r,e,a,c,u,o,s,h,i,l,v;r=Z,e=Z,a=Z,f.test(t.charAt(Z))?(c=t.charAt(Z),Z++):(c=n,0===et&&st(A));c===n&&(c=null);if(c!==n){if(u=[],p.test(t.charAt(Z))?(o=t.charAt(Z),Z++):(o=n,0===et&&st(g)),o!==n)for(;o!==n;)u.push(o),p.test(t.charAt(Z))?(o=t.charAt(Z),Z++):(o=n,0===et&&st(g));else u=n;if(u!==n){if(o=Z,46===t.charCodeAt(Z)?(s=".",Z++):(s=n,0===et&&st(d)),s!==n){if(h=[],p.test(t.charAt(Z))?(i=t.charAt(Z),Z++):(i=n,0===et&&st(g)),i!==n)for(;i!==n;)h.push(i),p.test(t.charAt(Z))?(i=t.charAt(Z),Z++):(i=n,0===et&&st(g));else h=n;h!==n?o=s=[s,h]:(Z=o,o=n)}else Z=o,o=n;if(o===n&&(o=null),o!==n){if(s=Z,101===t.charCodeAt(Z)?(h="e",Z++):(h=n,0===et&&st(C)),h!==n)if(f.test(t.charAt(Z))?(i=t.charAt(Z),Z++):(i=n,0===et&&st(A)),i===n&&(i=null),i!==n){if(l=[],p.test(t.charAt(Z))?(v=t.charAt(Z),Z++):(v=n,0===et&&st(g)),v!==n)for(;v!==n;)l.push(v),p.test(t.charAt(Z))?(v=t.charAt(Z),Z++):(v=n,0===et&&st(g));else l=n;l!==n?s=h=[h,i,l]:(Z=s,s=n)}else Z=s,s=n;else Z=s,s=n;s===n&&(s=null),s!==n?a=c=[c,u,o,s]:(Z=a,a=n)}else Z=a,a=n}else Z=a,a=n}else Z=a,a=n;e=a!==n?t.substring(e,Z):a;e!==n&&(r,e=Number(e));return r=e}())===n&&(r=ft()),r}function ft(){var r,e,a,c,u,o,s,h,i,l,f,A,p;if(et++,r=Z,34===t.charCodeAt(Z)?(e=x,Z++):(e=n,0===et&&st(b)),e!==n){for(a=[],c=Z,92===t.charCodeAt(Z)?(u=y,Z++):(u=n,0===et&&st(m)),u!==n?(34===t.charCodeAt(Z)?(o=x,Z++):(o=n,0===et&&st(b)),o===n&&(92===t.charCodeAt(Z)?(o=y,Z++):(o=n,0===et&&st(m)),o===n&&(47===t.charCodeAt(Z)?(o="/",Z++):(o=n,0===et&&st(E)),o===n&&(o=Z,110===t.charCodeAt(Z)?(s="n",Z++):(s=n,0===et&&st(S)),s!==n&&(o,s="\n"),(o=s)===n&&(o=Z,114===t.charCodeAt(Z)?(s="r",Z++):(s=n,0===et&&st($)),s!==n&&(o,s="\r"),(o=s)===n&&(o=Z,116===t.charCodeAt(Z)?(s="t",Z++):(s=n,0===et&&st(F)),s!==n&&(o,s="\t"),(o=s)===n&&(o=Z,98===t.charCodeAt(Z)?(s="b",Z++):(s=n,0===et&&st(w)),s!==n&&(o,s="\b"),(o=s)===n&&(o=Z,102===t.charCodeAt(Z)?(s="f",Z++):(s=n,0===et&&st(R)),s!==n&&(o,s="\f"),(o=s)===n&&(o=Z,117===t.charCodeAt(Z)?(s="u",Z++):(s=n,0===et&&st(j)),s!==n?(h=Z,i=Z,k.test(t.charAt(Z))?(l=t.charAt(Z),Z++):(l=n,0===et&&st(M)),l!==n?(k.test(t.charAt(Z))?(f=t.charAt(Z),Z++):(f=n,0===et&&st(M)),f!==n?(k.test(t.charAt(Z))?(A=t.charAt(Z),Z++):(A=n,0===et&&st(M)),A!==n?(k.test(t.charAt(Z))?(p=t.charAt(Z),Z++):(p=n,0===et&&st(M)),p!==n?i=l=[l,f,A,p]:(Z=i,i=n)):(Z=i,i=n)):(Z=i,i=n)):(Z=i,i=n),(h=i!==n?t.substring(h,Z):i)!==n?(o,o=s=N(h)):(Z=o,o=n)):(Z=o,o=n))))))))),o!==n?(c,c=u=o):(Z=c,c=n)):(Z=c,c=n),c===n&&(T.test(t.charAt(Z))?(c=t.charAt(Z),Z++):(c=n,0===et&&st(I)));c!==n;)a.push(c),c=Z,92===t.charCodeAt(Z)?(u=y,Z++):(u=n,0===et&&st(m)),u!==n?(34===t.charCodeAt(Z)?(o=x,Z++):(o=n,0===et&&st(b)),o===n&&(92===t.charCodeAt(Z)?(o=y,Z++):(o=n,0===et&&st(m)),o===n&&(47===t.charCodeAt(Z)?(o="/",Z++):(o=n,0===et&&st(E)),o===n&&(o=Z,110===t.charCodeAt(Z)?(s="n",Z++):(s=n,0===et&&st(S)),s!==n&&(o,s="\n"),(o=s)===n&&(o=Z,114===t.charCodeAt(Z)?(s="r",Z++):(s=n,0===et&&st($)),s!==n&&(o,s="\r"),(o=s)===n&&(o=Z,116===t.charCodeAt(Z)?(s="t",Z++):(s=n,0===et&&st(F)),s!==n&&(o,s="\t"),(o=s)===n&&(o=Z,98===t.charCodeAt(Z)?(s="b",Z++):(s=n,0===et&&st(w)),s!==n&&(o,s="\b"),(o=s)===n&&(o=Z,102===t.charCodeAt(Z)?(s="f",Z++):(s=n,0===et&&st(R)),s!==n&&(o,s="\f"),(o=s)===n&&(o=Z,117===t.charCodeAt(Z)?(s="u",Z++):(s=n,0===et&&st(j)),s!==n?(h=Z,i=Z,k.test(t.charAt(Z))?(l=t.charAt(Z),Z++):(l=n,0===et&&st(M)),l!==n?(k.test(t.charAt(Z))?(f=t.charAt(Z),Z++):(f=n,0===et&&st(M)),f!==n?(k.test(t.charAt(Z))?(A=t.charAt(Z),Z++):(A=n,0===et&&st(M)),A!==n?(k.test(t.charAt(Z))?(p=t.charAt(Z),Z++):(p=n,0===et&&st(M)),p!==n?i=l=[l,f,A,p]:(Z=i,i=n)):(Z=i,i=n)):(Z=i,i=n)):(Z=i,i=n),(h=i!==n?t.substring(h,Z):i)!==n?(o,o=s=N(h)):(Z=o,o=n)):(Z=o,o=n))))))))),o!==n?(c,c=u=o):(Z=c,c=n)):(Z=c,c=n),c===n&&(T.test(t.charAt(Z))?(c=t.charAt(Z),Z++):(c=n,0===et&&st(I)));a!==n?(34===t.charCodeAt(Z)?(c=x,Z++):(c=n,0===et&&st(b)),c!==n?(r,r=e=a.join("")):(Z=r,r=n)):(Z=r,r=n)}else Z=r,r=n;return et--,r===n&&(e=n,0===et&&st(v)),r}function At(){var r,e;return r=Z,gt()!==n?(58===t.charCodeAt(Z)?(e=":",Z++):(e=n,0===et&&st(D)),e!==n&>()!==n?(r,r=void 0):(Z=r,r=n)):(Z=r,r=n),r}function pt(){var r,e;return r=Z,gt()!==n?(44===t.charCodeAt(Z)?(e=",",Z++):(e=n,0===et&&st(G)),e!==n&>()!==n?(r,r=void 0):(Z=r,r=n)):(Z=r,r=n),r}function gt(){var r,e,a;for(r=Z,e=[],H.test(t.charAt(Z))?(a=t.charAt(Z),Z++):(a=n,0===et&&st(K)),a===n&&(a=dt())===n&&(a=Ct());a!==n;)e.push(a),H.test(t.charAt(Z))?(a=t.charAt(Z),Z++):(a=n,0===et&&st(K)),a===n&&(a=dt())===n&&(a=Ct());return e!==n&&(r,e=void 0),r=e}function dt(){var r,e,a,c,u,o,s;if(r=Z,e=Z,"//"===t.substr(Z,2)?(a="//",Z+=2):(a=n,0===et&&st(L)),a!==n){for(c=[],u=Z,o=Z,et++,P.test(t.charAt(Z))?(s=t.charAt(Z),Z++):(s=n,0===et&&st(Q)),et--,s===n?o=void 0:(Z=o,o=n),o!==n?(t.length>Z?(s=t.charAt(Z),Z++):(s=n,0===et&&st(V)),s!==n?u=o=[o,s]:(Z=u,u=n)):(Z=u,u=n);u!==n;)c.push(u),u=Z,o=Z,et++,P.test(t.charAt(Z))?(s=t.charAt(Z),Z++):(s=n,0===et&&st(Q)),et--,s===n?o=void 0:(Z=o,o=n),o!==n?(t.length>Z?(s=t.charAt(Z),Z++):(s=n,0===et&&st(V)),s!==n?u=o=[o,s]:(Z=u,u=n)):(Z=u,u=n);c!==n?e=a=[a,c]:(Z=e,e=n)}else Z=e,e=n;return r=e!==n?t.substring(r,Z):e}function Ct(){var r,e,a,c,u,o,s;if(r=Z,e=Z,"/*"===t.substr(Z,2)?(a="/*",Z+=2):(a=n,0===et&&st(W)),a!==n){for(c=[],u=Z,o=Z,et++,t.substr(Z,2)===X?(s=X,Z+=2):(s=n,0===et&&st(Y)),et--,s===n?o=void 0:(Z=o,o=n),o!==n?(t.length>Z?(s=t.charAt(Z),Z++):(s=n,0===et&&st(V)),s!==n?u=o=[o,s]:(Z=u,u=n)):(Z=u,u=n);u!==n;)c.push(u),u=Z,o=Z,et++,t.substr(Z,2)===X?(s=X,Z+=2):(s=n,0===et&&st(Y)),et--,s===n?o=void 0:(Z=o,o=n),o!==n?(t.length>Z?(s=t.charAt(Z),Z++):(s=n,0===et&&st(V)),s!==n?u=o=[o,s]:(Z=u,u=n)):(Z=u,u=n);c!==n?(t.substr(Z,2)===X?(u=X,Z+=2):(u=n,0===et&&st(Y)),u!==n?e=a=[a,c,u]:(Z=e,e=n)):(Z=e,e=n)}else Z=e,e=n;return r=e!==n?t.substring(r,Z):e}if((e=c())!==n&&Z===t.length)return e;throw e!==n&&Z<t.length&&st({type:"end"}),ht(rt,tt<t.length?t.charAt(tt):null,tt<t.length?ot(tt,tt+1):ot(tt,tt))}peg$subclass(peg$SyntaxError,Error),peg$SyntaxError.buildMessage=function(t,r){var e={literal:function(t){return'"'+a(t.text)+'"'},class:function(t){var r,e="";for(r=0;r<t.parts.length;r++)e+=t.parts[r]instanceof Array?c(t.parts[r][0])+"-"+c(t.parts[r][1]):c(t.parts[r]);return"["+(t.inverted?"^":"")+e+"]"},any:function(t){return"any character"},end:function(t){return"end of input"},other:function(t){return t.description}};function n(t){return t.charCodeAt(0).toString(16).toUpperCase()}function a(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,(function(t){return"\\x0"+n(t)})).replace(/[\x10-\x1F\x7F-\x9F]/g,(function(t){return"\\x"+n(t)}))}function c(t){return t.replace(/\\/g,"\\\\").replace(/\]/g,"\\]").replace(/\^/g,"\\^").replace(/-/g,"\\-").replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,(function(t){return"\\x0"+n(t)})).replace(/[\x10-\x1F\x7F-\x9F]/g,(function(t){return"\\x"+n(t)}))}return"Expected "+function(t){var r,n,a,c=new Array(t.length);for(r=0;r<t.length;r++)c[r]=(a=t[r],e[a.type](a));if(c.sort(),c.length>0){for(r=1,n=1;r<c.length;r++)c[r-1]!==c[r]&&(c[n]=c[r],n++);c.length=n}switch(c.length){case 1:return c[0];case 2:return c[0]+" or "+c[1];default:return c.slice(0,-1).join(", ")+", or "+c[c.length-1]}}(t)+" but "+function(t){return t?'"'+a(t)+'"':"end of input"}(r)+" found."},module.exports={SyntaxError:peg$SyntaxError,parse:peg$parse};
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @typedef { import("eslint").AST.SourceLocation } SourceLocation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const cds = require("@sap/cds");
|
|
9
|
+
const { SourceCode } = require("eslint");
|
|
10
|
+
const { isTest } = require("./helpers");
|
|
11
|
+
|
|
12
|
+
const cache = new Map();
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
/**
|
|
16
|
+
* Simple cache to store model and any cds calls made in the rule creation
|
|
17
|
+
* api to modify the model
|
|
18
|
+
*/
|
|
19
|
+
Cache: {
|
|
20
|
+
has(key) {
|
|
21
|
+
return cache.has(key);
|
|
22
|
+
},
|
|
23
|
+
set(key, value) {
|
|
24
|
+
return cache.set(key, [value, Date.now()]);
|
|
25
|
+
},
|
|
26
|
+
get(key) {
|
|
27
|
+
if (cache.get(key)) {
|
|
28
|
+
return cache.get(key)[0];
|
|
29
|
+
} else {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
dump() {
|
|
34
|
+
const dump = {};
|
|
35
|
+
for (const [key, value] of cache.entries()) {
|
|
36
|
+
const timestamp = new Date(value[1]);
|
|
37
|
+
dump[key] = { key, value: JSON.stringify(value[0]), timestamp };
|
|
38
|
+
}
|
|
39
|
+
return dump;
|
|
40
|
+
},
|
|
41
|
+
getModels() {
|
|
42
|
+
const models = [];
|
|
43
|
+
for (const key of cache.keys()) {
|
|
44
|
+
if (key.startsWith("model:")) {
|
|
45
|
+
models.push(key.replace("model:", ""));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return models;
|
|
49
|
+
},
|
|
50
|
+
remove(key) {
|
|
51
|
+
if (cache.has(key)) {
|
|
52
|
+
cache.delete(key);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
},
|
|
56
|
+
clear() {
|
|
57
|
+
cache.clear();
|
|
58
|
+
return;
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Takes care of all of the cds modeling:
|
|
64
|
+
* - Loads the model and assigns relevant files to it
|
|
65
|
+
* - Updates the model (according to 'type' events in the editor)
|
|
66
|
+
* - Updates the ESLint configuration file path (i.e. mono-repo with
|
|
67
|
+
* multiple models)
|
|
68
|
+
* @param context
|
|
69
|
+
* @returns
|
|
70
|
+
*/
|
|
71
|
+
updateCache: function (context) {
|
|
72
|
+
const filePath = context.filePath;
|
|
73
|
+
const code = context.code;
|
|
74
|
+
const cds = context.cds;
|
|
75
|
+
// Set configPath according to filePath
|
|
76
|
+
let configPath = path.dirname(module.exports.getConfigPath(filePath));
|
|
77
|
+
if (configPath) {
|
|
78
|
+
module.exports.Cache.set("projectpath", configPath);
|
|
79
|
+
module.exports.Cache.set("configpath", configPath);
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error("Failed to find an ESLint configuration file!");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Get cds model for current project
|
|
85
|
+
module.exports.loadModel(code, configPath, filePath);
|
|
86
|
+
|
|
87
|
+
// Update config path (any files not part of the above model)
|
|
88
|
+
// Can only do this if model.$sources are known, otherwise no
|
|
89
|
+
// way to distinguish between 'model' vs 'outsider' files
|
|
90
|
+
if (cds && cds.model && !cds.model.err) {
|
|
91
|
+
module.exports.updateConfigPath(code, configPath, filePath);
|
|
92
|
+
configPath = module.exports.Cache.get("configpath");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Update cds model on every 'type' event (from the editor)
|
|
96
|
+
if (
|
|
97
|
+
module.exports.Cache.has(`file:${filePath}`) &&
|
|
98
|
+
code !== module.exports.Cache.get(`file:${filePath}`)
|
|
99
|
+
) {
|
|
100
|
+
// Update file contents in Cache
|
|
101
|
+
module.exports.Cache.set(`file:${filePath}`, code);
|
|
102
|
+
module.exports.Cache.remove(`done:${configPath}:${context.ruleID}`);
|
|
103
|
+
context.code = code;
|
|
104
|
+
context.sourcecode = new SourceCode(code, module.exports.getAST(code));
|
|
105
|
+
module.exports.updateModel(context);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get cds environment (when called from ESLint's ruleTester)
|
|
109
|
+
if (
|
|
110
|
+
context.options &&
|
|
111
|
+
context.options[0] &&
|
|
112
|
+
context.options[0].environment
|
|
113
|
+
) {
|
|
114
|
+
module.exports.Cache.set(`environment`, context.options[0].environment);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
context.configPath = configPath;
|
|
118
|
+
return context;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Generates dummy AST with just single Program node
|
|
123
|
+
* @param code Parse file contents
|
|
124
|
+
* @returns AST
|
|
125
|
+
*/
|
|
126
|
+
getAST: function (code) {
|
|
127
|
+
return {
|
|
128
|
+
type: "Program",
|
|
129
|
+
body: [],
|
|
130
|
+
sourceType: "module",
|
|
131
|
+
tokens: [],
|
|
132
|
+
comments: [],
|
|
133
|
+
range: [0, code.length],
|
|
134
|
+
loc: {
|
|
135
|
+
start: {
|
|
136
|
+
line: 1,
|
|
137
|
+
column: 0,
|
|
138
|
+
},
|
|
139
|
+
end: {
|
|
140
|
+
line: 1,
|
|
141
|
+
column: 0,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generates proxy for cds object which adds caching
|
|
149
|
+
* @param obj cds object
|
|
150
|
+
* @returns Proxy for cds
|
|
151
|
+
*/
|
|
152
|
+
getCDSProxy: function (obj) {
|
|
153
|
+
const handler = {
|
|
154
|
+
get(target, prop, receiver) {
|
|
155
|
+
const value = Reflect.get(target, prop, receiver);
|
|
156
|
+
if (["model", "environment"].includes(prop)) {
|
|
157
|
+
if (prop === "model") {
|
|
158
|
+
prop = `model:${module.exports.Cache.get("configpath")}`;
|
|
159
|
+
}
|
|
160
|
+
return module.exports.Cache.get(prop);
|
|
161
|
+
}
|
|
162
|
+
if (typeof value !== "object") {
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
/*eslint no-extra-boolean-cast: "off"*/
|
|
166
|
+
if (!!value) {
|
|
167
|
+
return new Proxy(value, handler);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
err: `Property ${prop} prop does not exist on object ${obj}!`,
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
apply(target, thisArg, argumentsList) {
|
|
174
|
+
const result = Reflect.apply(target, this, argumentsList);
|
|
175
|
+
return result;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
return new Proxy(obj, handler);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Converts code with {line, column} to ESLint's 'range' property:
|
|
183
|
+
* https://eslint.org/docs/developer-guide/working-with-custom-parsers#all-nodes
|
|
184
|
+
* code.slice(node.range[0], node.range[1]) must be the text of the node!
|
|
185
|
+
* @param code source code
|
|
186
|
+
* @param line line number
|
|
187
|
+
* @param column column number
|
|
188
|
+
* @returns ESLint range
|
|
189
|
+
*/
|
|
190
|
+
getRange: function (code, line, column) {
|
|
191
|
+
let lines;
|
|
192
|
+
if (typeof code === "string") {
|
|
193
|
+
lines = SourceCode.splitLines(code);
|
|
194
|
+
} else {
|
|
195
|
+
lines = code;
|
|
196
|
+
}
|
|
197
|
+
const ranges = [0];
|
|
198
|
+
lines.forEach((line, i) => {
|
|
199
|
+
if (i === 0) {
|
|
200
|
+
ranges[i + 1] = line.length + 1;
|
|
201
|
+
} else {
|
|
202
|
+
ranges[i + 1] = ranges[i] + line.length + 1;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (line > 1) {
|
|
206
|
+
return ranges[line - 1] + column;
|
|
207
|
+
} else {
|
|
208
|
+
return column;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Uses ESLint's static function splitLines() to split the source code text
|
|
214
|
+
* into an array of lines:
|
|
215
|
+
* https://eslint.org/docs/developer-guide/nodejs-api#sourcecodesplitlines
|
|
216
|
+
* Returns the index of the last line
|
|
217
|
+
* @param code
|
|
218
|
+
* @returns Last line index
|
|
219
|
+
*/
|
|
220
|
+
getLastLine: function (code) {
|
|
221
|
+
let lines;
|
|
222
|
+
if (typeof code === "string") {
|
|
223
|
+
lines = SourceCode.splitLines(code);
|
|
224
|
+
} else {
|
|
225
|
+
lines = code;
|
|
226
|
+
}
|
|
227
|
+
return lines.length - 1;
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generates ESlint's 'loc' from artifact string and cds $location property:
|
|
232
|
+
* https://eslint.org/docs/developer-guide/working-with-rules-deprecated#contextreport
|
|
233
|
+
* @param name
|
|
234
|
+
* @param {SoureLocation} obj
|
|
235
|
+
* @returns ESLint's 'loc' object
|
|
236
|
+
*/
|
|
237
|
+
getLocation: function (name, obj) {
|
|
238
|
+
const loc = {
|
|
239
|
+
start: { line: 0, column: 0 },
|
|
240
|
+
end: { line: 1, column: 0 },
|
|
241
|
+
};
|
|
242
|
+
if (obj.$location) {
|
|
243
|
+
const nameloc = obj.$location;
|
|
244
|
+
// CSN entry with column 0 is equivalent to 'undefined'
|
|
245
|
+
// It means that the column in that line cannot be determined,
|
|
246
|
+
// so we assign a value 1 so as not to get a negative value
|
|
247
|
+
if (nameloc.col === 0) {
|
|
248
|
+
nameloc.col = 1;
|
|
249
|
+
}
|
|
250
|
+
loc.start.column = nameloc.col - 1;
|
|
251
|
+
loc.start.line = nameloc.line;
|
|
252
|
+
loc.end.column = nameloc.col - 1 + name.length;
|
|
253
|
+
loc.end.line = nameloc.line;
|
|
254
|
+
}
|
|
255
|
+
return loc;
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Searches for ESLint config file types (in order or precedence)
|
|
260
|
+
* and returns corresponding directory (usually project's root dir)
|
|
261
|
+
* https://eslint.org/docs/user-guide/configuring#configuration-file-formats
|
|
262
|
+
* @param {string} currentDir start here and search until root dir
|
|
263
|
+
* @returns {string} dir containing ESLint config file (empty if not exists)
|
|
264
|
+
*/
|
|
265
|
+
getConfigPath: function (currentDir = ".") {
|
|
266
|
+
const configFiles = [
|
|
267
|
+
".eslintrc.js",
|
|
268
|
+
".eslintrc.cjs",
|
|
269
|
+
".eslintrc.yaml",
|
|
270
|
+
".eslintrc.yml",
|
|
271
|
+
".eslintrc.json",
|
|
272
|
+
".eslintrc",
|
|
273
|
+
"package.json",
|
|
274
|
+
];
|
|
275
|
+
let configDir = path.resolve(currentDir);
|
|
276
|
+
while (configDir !== path.resolve(configDir, "..")) {
|
|
277
|
+
for (let i = 0; i < configFiles.length; i++) {
|
|
278
|
+
const configPath = path.join(configDir, configFiles[i]);
|
|
279
|
+
if (fs.existsSync(configPath) && fs.statSync(configPath).isFile()) {
|
|
280
|
+
return configPath;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
configDir = path.join(configDir, "..");
|
|
284
|
+
}
|
|
285
|
+
return "";
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Loads LinkedCSN cds model by:
|
|
290
|
+
* (1) Determining config path if does not exist
|
|
291
|
+
* (2) Running cds.load('*') to resolve full model
|
|
292
|
+
* (3) If 2. also fails, passing on the error object
|
|
293
|
+
* @param code
|
|
294
|
+
* @returns
|
|
295
|
+
*/
|
|
296
|
+
loadModel: function (code = "", configPath, filePath) {
|
|
297
|
+
let compiledModel;
|
|
298
|
+
let reflectedModel;
|
|
299
|
+
if (isTest()) {
|
|
300
|
+
if (code) {
|
|
301
|
+
try {
|
|
302
|
+
if (isTest()) {
|
|
303
|
+
compiledModel = cds.compile.to.csn(code, {
|
|
304
|
+
sync: true,
|
|
305
|
+
locations: true,
|
|
306
|
+
});
|
|
307
|
+
} else {
|
|
308
|
+
compiledModel = cds.compile.to.csn([filePath], {
|
|
309
|
+
sync: true,
|
|
310
|
+
locations: true,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (compiledModel) {
|
|
314
|
+
reflectedModel = cds.linked(compiledModel);
|
|
315
|
+
}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
reflectedModel = { err };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
// Loads new model (must clear cache in order to be able to change root with every configPath)
|
|
322
|
+
cds.resolve.cache = {};
|
|
323
|
+
const roots = cds.resolve("*", { root: configPath });
|
|
324
|
+
if (
|
|
325
|
+
!module.exports.Cache.has(`model:${configPath}`) &&
|
|
326
|
+
configPath !== filePath
|
|
327
|
+
) {
|
|
328
|
+
if (roots) {
|
|
329
|
+
try {
|
|
330
|
+
compiledModel = cds.load(roots, {
|
|
331
|
+
cwd: configPath,
|
|
332
|
+
sync: true,
|
|
333
|
+
locations: true,
|
|
334
|
+
});
|
|
335
|
+
if (compiledModel) {
|
|
336
|
+
reflectedModel = cds.linked(compiledModel);
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
reflectedModel = { err };
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
try {
|
|
343
|
+
compiledModel = cds.compile.to.csn([filePath], {
|
|
344
|
+
sync: true,
|
|
345
|
+
locations: true,
|
|
346
|
+
});
|
|
347
|
+
if (compiledModel) {
|
|
348
|
+
reflectedModel = cds.linked(compiledModel);
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
reflectedModel = { err };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
reflectedModel = module.exports.Cache.get(`model:${configPath}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Cache model files
|
|
359
|
+
if (
|
|
360
|
+
reflectedModel &&
|
|
361
|
+
reflectedModel.$sources &&
|
|
362
|
+
!module.exports.Cache.has(`modelfiles:${configPath}`)
|
|
363
|
+
) {
|
|
364
|
+
const files = reflectedModel.$sources;
|
|
365
|
+
if (files && files.length > 0) {
|
|
366
|
+
module.exports.Cache.set(`modelfiles:${configPath}`, files);
|
|
367
|
+
if (!isTest()) {
|
|
368
|
+
files.forEach((file) => {
|
|
369
|
+
if (!module.exports.Cache.has(`file:${file}`)) {
|
|
370
|
+
module.exports.Cache.set(
|
|
371
|
+
`file:${file}`,
|
|
372
|
+
fs.readFileSync(file, "utf8")
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
380
|
+
return;
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Updates configPath (usually ESLint's configPath) for a given project if:
|
|
385
|
+
* - File is not part of the compiled project model
|
|
386
|
+
* It then defaults to filePath and compiles the file stand-alone
|
|
387
|
+
* @param code
|
|
388
|
+
* @param configPath
|
|
389
|
+
* @param filePath
|
|
390
|
+
*/
|
|
391
|
+
updateConfigPath: function (code, configPath, filePath) {
|
|
392
|
+
let compiledModel;
|
|
393
|
+
let reflectedModel;
|
|
394
|
+
const files = module.exports.Cache.has(`modelfiles:${configPath}`)
|
|
395
|
+
? module.exports.Cache.get(`modelfiles:${configPath}`)
|
|
396
|
+
: [];
|
|
397
|
+
// 'Ousider' files: If a file is not part of cds model for this dir,
|
|
398
|
+
// it is compiled individually
|
|
399
|
+
if (!files || !files.includes(filePath)) {
|
|
400
|
+
module.exports.Cache.set(`configpath`, filePath);
|
|
401
|
+
module.exports.Cache.set(`modelfiles:${filePath}`, [filePath]);
|
|
402
|
+
module.exports.Cache.set(
|
|
403
|
+
`file:${filePath}`,
|
|
404
|
+
fs.readFileSync(filePath, "utf8")
|
|
405
|
+
);
|
|
406
|
+
if (!module.exports.Cache.has(`model:${filePath}`)) {
|
|
407
|
+
try {
|
|
408
|
+
if (isTest()) {
|
|
409
|
+
compiledModel = cds.compile.to.csn(code, {
|
|
410
|
+
sync: true,
|
|
411
|
+
locations: true,
|
|
412
|
+
});
|
|
413
|
+
} else {
|
|
414
|
+
compiledModel = cds.compile.to.csn([filePath], {
|
|
415
|
+
sync: true,
|
|
416
|
+
locations: true,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (compiledModel) {
|
|
420
|
+
reflectedModel = cds.linked(compiledModel);
|
|
421
|
+
}
|
|
422
|
+
} catch (err) {
|
|
423
|
+
reflectedModel = { err };
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
reflectedModel = module.exports.Cache.get(`model:${filePath}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
module.exports.Cache.set(`model:${filePath}`, reflectedModel);
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Updates compiled model (CSN) by:
|
|
434
|
+
* 1. Getting model files from CSN.$sources (cached)
|
|
435
|
+
* 2. Running compile.to.csn with updated sources dictionary
|
|
436
|
+
* @param code
|
|
437
|
+
*/
|
|
438
|
+
updateModel: function (context) {
|
|
439
|
+
const configPath = context.configPath;
|
|
440
|
+
const code = context.code;
|
|
441
|
+
let compiledModel;
|
|
442
|
+
let reflectedModel;
|
|
443
|
+
let files = [];
|
|
444
|
+
const dictFiles = {};
|
|
445
|
+
if (module.exports.Cache.has(`modelfiles:${configPath}`)) {
|
|
446
|
+
files = module.exports.Cache.get(`modelfiles:${configPath}`);
|
|
447
|
+
if (files.length > 1) {
|
|
448
|
+
files.forEach((file) => {
|
|
449
|
+
if (module.exports.Cache.has(`file:${file}`)) {
|
|
450
|
+
dictFiles[file] = module.exports.Cache.get(`file:${file}`);
|
|
451
|
+
} else {
|
|
452
|
+
dictFiles[file] = fs.readFileSync(file, "utf8");
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
try {
|
|
456
|
+
/** Ignore typings here as the options 'sync' and 'cwd'
|
|
457
|
+
* should not be visible in the public api! */
|
|
458
|
+
compiledModel = cds.compile.to.csn(dictFiles, {
|
|
459
|
+
sync: true,
|
|
460
|
+
locations: true,
|
|
461
|
+
});
|
|
462
|
+
if (compiledModel) {
|
|
463
|
+
reflectedModel = cds.linked(compiledModel);
|
|
464
|
+
}
|
|
465
|
+
} catch (err) {
|
|
466
|
+
reflectedModel = { err };
|
|
467
|
+
}
|
|
468
|
+
} else if (files.length === 1) {
|
|
469
|
+
try {
|
|
470
|
+
compiledModel = cds.compile.to.csn(code, {
|
|
471
|
+
sync: true,
|
|
472
|
+
locations: true,
|
|
473
|
+
});
|
|
474
|
+
if (compiledModel) {
|
|
475
|
+
reflectedModel = cds.linked(compiledModel);
|
|
476
|
+
}
|
|
477
|
+
} catch (err) {
|
|
478
|
+
reflectedModel = { err };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
module.exports.Cache.set(`model:${configPath}`, reflectedModel);
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
|