@sap/cds-dk 8.9.5 → 9.0.5
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 +101 -2012
- package/LICENSE +15 -21
- package/bin/bind.js +1 -1
- package/bin/build.js +35 -37
- package/bin/cds.js +13 -30
- package/bin/compile/index.js +2 -2
- package/bin/debug.js +27 -7
- package/bin/deploy/index.js +6 -23
- package/bin/deploy/to-hana.js +4 -2
- package/bin/help.js +5 -3
- package/bin/init.js +1 -1
- package/bin/up.js +45 -12
- package/bin/watch.js +45 -63
- package/lib/bind/cf.js +2 -1
- package/lib/bind/k8s.js +2 -2
- package/lib/bind/plugin.js +1 -1
- package/lib/bind/shared.js +16 -25
- package/lib/build/buildTaskEngine.js +26 -10
- package/lib/build/buildTaskFactory.js +3 -2
- package/lib/build/buildTaskProvider.js +18 -7
- package/lib/build/buildTaskProviderFactory.js +35 -13
- package/lib/build/constants.js +4 -0
- package/lib/build/index.js +1 -1
- package/lib/build/plugin.js +89 -54
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +3 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +4 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +4 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +18 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +0 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +34 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +9 -2
- package/lib/build/plugins/helm/index.js +2 -1
- package/lib/build/plugins.js +24 -10
- package/lib/build/provider/edmxBuildPlugin.js +1 -11
- package/lib/build/provider/fiori/index.js +4 -4
- package/lib/build/provider/hana/index.js +12 -5
- package/lib/build/provider/hana/template/package.json +1 -1
- package/lib/build/provider/internalBuildPlugin.js +1 -1
- package/lib/build/provider/internalBuildTaskProvider.js +3 -24
- package/lib/build/provider/java/index.js +1 -1
- package/lib/build/provider/mtx-extension/index.js +15 -8
- package/lib/build/provider/nodejs/index.js +28 -6
- package/lib/build/util.js +186 -125
- package/lib/deploy/index.js +5 -0
- package/lib/deploy/to-hana/hana.js +27 -33
- package/lib/deploy/to-hana/hdiDeployUtil.js +2 -2
- package/lib/import/odata/v2parser.js +34 -3
- package/lib/import/openapi/importOpenAPI.js +4 -0
- package/lib/index.js +2 -11
- package/lib/init/constants.js +1 -2
- package/lib/init/merge.js +101 -77
- package/lib/init/mvn.js +3 -3
- package/lib/init/projectReader.js +1 -1
- package/lib/init/template/ams/files/mta.yaml.hbs +6 -1
- package/lib/init/template/ams/files/package.json.hbs +1 -1
- package/lib/init/template/approuter/files/mta.yaml.hbs +5 -0
- package/lib/init/template/cf-manifest/files/manifest.yml.hbs +1 -1
- package/lib/init/template/cloud-logging/files/package.json.hbs +3 -3
- package/lib/init/template/data/as-json.js +3 -3
- package/lib/init/template/dynatrace/files/mta.yaml.hbs +1 -1
- package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
- package/lib/init/template/dynatrace/index.js +2 -1
- package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +6 -0
- package/lib/init/template/enterprise-messaging/index.js +5 -1
- package/lib/init/template/extensibility/files/package.json.hbs +1 -1
- package/lib/init/template/hana/files/db/package.json +1 -1
- package/lib/init/template/hana/files/package.json.hbs +2 -4
- package/lib/init/template/hana/index.js +2 -2
- package/lib/init/template/handler/index.js +16 -12
- package/lib/init/template/helm/index.js +13 -22
- package/lib/init/template/helm-unified-runtime/index.js +2 -1
- package/lib/init/template/html5-repo/files/values.yaml.hbs +12 -1
- package/lib/init/template/html5-repo/index.js +10 -5
- package/lib/init/template/http/index.js +1 -1
- package/lib/init/template/ias/files/mta.yaml.hbs +1 -1
- package/lib/init/template/ias/files/sidecar.package.json.hbs +0 -7
- package/lib/init/template/ias/index.js +5 -4
- package/lib/init/template/lint/files/package.json +6 -0
- package/lib/init/template/lint/index.js +61 -204
- package/lib/init/template/mta/files/package.json +1 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +2 -2
- package/lib/init/template/multitenancy/files/package.json.hbs +1 -1
- package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +7 -4
- package/lib/init/template/multitenancy/index.js +3 -0
- package/lib/init/template/nodejs/files/package.json.hbs +6 -3
- package/lib/init/template/postgres/files/package.json.hbs +1 -1
- package/lib/init/template/redis/files/package.json.hbs +1 -1
- package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +6 -0
- package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -0
- package/lib/init/template/sample/files/app/browse/fiori-service.cds +3 -2
- package/lib/init/template/sample/files/app/common.cds +0 -11
- package/lib/init/template/sqlite/files/package.json +1 -1
- package/lib/init/template/toggles/files/package.json.hbs +1 -1
- package/lib/init/template/typescript/files/package.json +2 -4
- package/lib/init/template/workzone-standard/index.js +2 -0
- package/lib/init/template/xsuaa/index.js +2 -6
- package/lib/lint/Linter.js +203 -331
- package/lib/lint/generators.js +52 -46
- package/lib/lint/io.js +77 -289
- package/lib/util/fs.js +2 -1
- package/lib/util/git.js +1 -1
- package/lib/util/glob.js +77 -0
- package/lib/watch/livereload-server.js +3 -4
- package/lib/watch/node-watch.js +3 -4
- package/lib/watch/watched.js +1 -1
- package/npm-shrinkwrap.json +200 -858
- package/package.json +6 -8
- package/readme.md +1 -1
- package/lib/lint/checks.js +0 -135
package/lib/lint/Linter.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
const term = require(
|
|
2
|
-
const io = require(
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
1
|
+
const term = require('../util/term')
|
|
2
|
+
const io = require('./io')
|
|
3
|
+
const generators = require('./generators')
|
|
4
|
+
const cds = require('../../lib/cds')
|
|
5
|
+
const { path, isfile, readdir } = cds.utils
|
|
6
|
+
const { exit } = require('node:process')
|
|
7
|
+
const { pathToFileURL } = require('node:url')
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
9
|
+
|
|
10
|
+
const LOG = cds.debug('lint')
|
|
11
|
+
const LOG_CONFIG = cds.debug('lint:config')
|
|
12
|
+
const GENERAL_ERROR = 1
|
|
13
|
+
const FATAL_ERROR = 2
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {{errorCount: number, fatalErrorCount: number}[]} results
|
|
17
|
+
*/
|
|
18
|
+
const countErrors = results => results.reduce((acc, result) => {
|
|
19
|
+
acc.errorCount += result.errorCount
|
|
20
|
+
acc.fatalErrorCount += result.fatalErrorCount
|
|
21
|
+
return acc
|
|
22
|
+
}, { errorCount: 0, fatalErrorCount: 0 })
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* Safely require a module, return empty object if module not found
|
|
@@ -19,185 +29,141 @@ const FATAL_ERROR = 2;
|
|
|
19
29
|
*/
|
|
20
30
|
async function tryRequire(module, esm = false) {
|
|
21
31
|
try {
|
|
22
|
-
|
|
32
|
+
const modulePath = require.resolve(module, { paths: [process.cwd()] })
|
|
33
|
+
const result = esm
|
|
34
|
+
// pathToFileURL is used to convert the path to a file URL on Windows
|
|
35
|
+
? (await import(pathToFileURL(modulePath))).default
|
|
36
|
+
: require(modulePath)
|
|
37
|
+
return result
|
|
23
38
|
} catch (e) {
|
|
24
|
-
if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(module))
|
|
39
|
+
if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(module))
|
|
40
|
+
return {}
|
|
25
41
|
// target module is either .mjs (etc) or user project is ESM
|
|
26
|
-
if (['ERR_REQUIRE_ASYNC_MODULE', 'ERR_REQUIRE_ESM'].includes(e.code))
|
|
42
|
+
if (['ERR_REQUIRE_ASYNC_MODULE', 'ERR_REQUIRE_ESM'].includes(e.code))
|
|
43
|
+
return tryRequire(module, true)
|
|
27
44
|
throw e
|
|
28
45
|
}
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param {string} currentDir - current directory to start search
|
|
34
|
-
* @returns {Promise<ReturnType<NodeRequire>>} - ESLint module
|
|
35
|
-
*/
|
|
36
|
-
async function _loadEslint(currentDir) {
|
|
37
|
-
const { loadESLint, ESLint } = require('eslint')
|
|
38
|
-
const DefaultESLint = loadESLint
|
|
39
|
-
? await loadESLint({ cwd: currentDir }) // ESLint 9
|
|
40
|
-
: new ESLint({ cwd: currentDir }) // ESLint 8
|
|
41
|
-
return DefaultESLint;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @param {string[]} configFiles - possible config file paths
|
|
49
|
+
* Ascends through directory tree to find the first config file.
|
|
46
50
|
* @param {string} currentDir - current directory to start search
|
|
47
51
|
* @returns {string} - path to config file
|
|
48
52
|
*/
|
|
49
|
-
async function findConfigPath (
|
|
53
|
+
async function findConfigPath (currentDir = '.') {
|
|
54
|
+
// possible config file paths
|
|
55
|
+
const configFiles = [
|
|
56
|
+
'eslint.config.js',
|
|
57
|
+
'eslint.config.cjs',
|
|
58
|
+
'eslint.config.mjs',
|
|
59
|
+
// the following 3 formats require additional setup by the user as of today,
|
|
60
|
+
// see https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files
|
|
61
|
+
'eslint.config.ts',
|
|
62
|
+
'eslint.config.mts',
|
|
63
|
+
'eslint.config.cts',
|
|
64
|
+
]
|
|
65
|
+
|
|
50
66
|
let configDir = path.resolve(currentDir)
|
|
51
67
|
while (configDir && configDir !== path.dirname(configDir)) {
|
|
52
68
|
for (const configFile of configFiles) {
|
|
53
69
|
const configPath = path.join(configDir, configFile)
|
|
54
70
|
const config = await tryRequire(configPath)
|
|
55
|
-
if (configFile !== 'package.json' || config?.eslintConfig) {
|
|
56
|
-
|
|
57
|
-
return configPath
|
|
58
|
-
}
|
|
71
|
+
if ((configFile !== 'package.json' || config?.eslintConfig) && isfile(configPath)) {
|
|
72
|
+
return configPath
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
|
-
configDir = path.dirname(configDir)
|
|
75
|
+
configDir = path.dirname(configDir)
|
|
62
76
|
}
|
|
63
77
|
return ''
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
/**
|
|
67
|
-
* This class and its subclasses are an attempt to smoothen out some of the
|
|
68
|
-
* workflow in the Linter class. It gets rid of some of the isESLintLegacy ? ... : ...
|
|
69
|
-
* assignments, although not all. The remaining branching is too closely coupled
|
|
70
|
-
* to some properties of the Linter class to be easily abstracted away.
|
|
71
|
-
* @abstract
|
|
72
|
-
*/
|
|
73
|
-
class ESLintLoader {
|
|
74
|
-
/**
|
|
75
|
-
* Searches for ESLint config file types (in order or precedence)
|
|
76
|
-
* and returns corresponding directory (usually project's root dir)
|
|
77
|
-
* https://eslint.org/docs/user-guide/configuring#configuration-file-formats
|
|
78
|
-
* @param {string} currentDir start here and search until root dir
|
|
79
|
-
* @returns {string} dir containing ESLint config file (empty if not exists)
|
|
80
|
-
* @abstract
|
|
81
|
-
*/
|
|
82
|
-
// eslint-disable-next-line no-unused-vars
|
|
83
|
-
async getConfigPath (currentDir = '.') { throw new Error("Not implemented") }
|
|
84
|
-
/** @type {import('./io').sanitizeEslintConfig} */
|
|
85
|
-
// eslint-disable-next-line no-unused-vars
|
|
86
|
-
sanitizeEslintConfig (configIn, customRuleExample, logger) { throw new Error("Not implemented") }
|
|
87
|
-
/** @type {import('./io').readEslintConfig} */
|
|
88
|
-
// eslint-disable-next-line no-unused-vars
|
|
89
|
-
readEslintConfig(configPath) { throw new Error("Not implemented") }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
class ESLint8Loader extends ESLintLoader {
|
|
93
|
-
async getConfigPath(currentDir = '.') {
|
|
94
|
-
return findConfigPath([
|
|
95
|
-
".eslintrc.js",
|
|
96
|
-
".eslintrc.cjs",
|
|
97
|
-
".eslintrc.yaml",
|
|
98
|
-
".eslintrc.yml",
|
|
99
|
-
".eslintrc.json",
|
|
100
|
-
".eslintrc",
|
|
101
|
-
"package.json",
|
|
102
|
-
], currentDir)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
sanitizeEslintConfig (configIn, customRuleExample, logger) {
|
|
106
|
-
return io.sanitizeEslintConfigLegacy(configIn, customRuleExample, logger)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
readEslintConfig(configPath) {
|
|
110
|
-
return io.readEslintConfigLegacy(configPath)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
class ESLint9Loader extends ESLintLoader {
|
|
115
|
-
async getConfigPath(currentDir = '.') {
|
|
116
|
-
return findConfigPath([
|
|
117
|
-
"eslint.config.js",
|
|
118
|
-
"eslint.config.cjs",
|
|
119
|
-
"eslint.config.mjs",
|
|
120
|
-
// the following 3 formats require additional setup by the user as of today,
|
|
121
|
-
// see https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files
|
|
122
|
-
"eslint.config.ts",
|
|
123
|
-
"eslint.config.mts",
|
|
124
|
-
"eslint.config.cts",
|
|
125
|
-
], currentDir)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
sanitizeEslintConfig (configIn, customRuleExample, logger) {
|
|
129
|
-
return io.sanitizeEslintConfig(configIn, customRuleExample, logger)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
readEslintConfig(configPath) {
|
|
133
|
-
return io.readEslintConfig(configPath)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
80
|
class Linter {
|
|
138
|
-
/** @type {string | undefined} */
|
|
139
|
-
eslintCmd = "";
|
|
140
81
|
/** @type {string} */
|
|
141
|
-
|
|
82
|
+
eslintCmd = 'npx eslint'
|
|
142
83
|
/** @type {string | string[]} */
|
|
143
|
-
eslintCmdFileExpr =
|
|
144
|
-
/** @type {boolean} */
|
|
145
|
-
isFile = false;
|
|
146
|
-
/** @type {string} */
|
|
147
|
-
help = "";
|
|
84
|
+
eslintCmdFileExpr = ''
|
|
148
85
|
/** @type {string} */
|
|
149
|
-
|
|
150
|
-
/** @type {string[]} */
|
|
151
|
-
flags = [];
|
|
86
|
+
help = ''
|
|
152
87
|
/** @type {string} */
|
|
153
|
-
|
|
88
|
+
debug = ''
|
|
89
|
+
/** @type {string[]} */
|
|
90
|
+
flags = []
|
|
91
|
+
/** @type {import('fs').PathLike} */
|
|
92
|
+
#configPath
|
|
93
|
+
async #getConfigPath () {
|
|
94
|
+
return this.#configPath ??= await findConfigPath(process.cwd())
|
|
95
|
+
}
|
|
154
96
|
/** @type {object} */
|
|
155
|
-
configContents
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
97
|
+
#configContents
|
|
98
|
+
async #getConfigContents () {
|
|
99
|
+
if (!this.#configContents) {
|
|
100
|
+
if (!(await this.#getConfigPath())) {
|
|
101
|
+
this.#configContents = io.sanitizeEslintConfig({}, LOG)
|
|
102
|
+
} else {
|
|
103
|
+
const cc = await io.readEslintConfig(await this.#getConfigPath())
|
|
104
|
+
this.#configContents = cc
|
|
105
|
+
const cdsConfig = Array.isArray(cc)
|
|
106
|
+
? cc?.find(c => c.files?.some(f => f.includes('*.cds'))) ?? {}
|
|
107
|
+
: cc
|
|
108
|
+
this.#configContents = cdsConfig.files
|
|
109
|
+
? {
|
|
110
|
+
...cdsConfig.plugins.configs?.recommended,
|
|
111
|
+
languageOptions: cdsConfig.languageOptions,
|
|
112
|
+
files: cdsConfig.files,
|
|
113
|
+
rules: cdsConfig.rules,
|
|
114
|
+
}
|
|
115
|
+
: {}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return this.#configContents
|
|
119
|
+
}
|
|
162
120
|
/** @type {string[]} */
|
|
163
|
-
fileExtensions = []
|
|
121
|
+
fileExtensions = []
|
|
164
122
|
/** @type {string} */
|
|
165
|
-
pluginPath
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
123
|
+
#pluginPath
|
|
124
|
+
async #getPluginPath () {
|
|
125
|
+
if (this.#pluginPath === undefined) {
|
|
126
|
+
try {
|
|
127
|
+
this.#pluginPath = require.resolve('@sap/eslint-plugin-cds', {
|
|
128
|
+
paths: [path.dirname(await this.#getConfigPath())],
|
|
129
|
+
})
|
|
130
|
+
} catch {
|
|
131
|
+
// CLI will report (plugin not installed)
|
|
132
|
+
this.#pluginPath = null
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return this.#pluginPath
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @type {NodeJS.Module} */
|
|
139
|
+
#pluginApi
|
|
140
|
+
async #getPluginApi () {
|
|
141
|
+
return this.#pluginApi ??= require((await this.#getPluginPath()).replace('index.js', 'api'))
|
|
142
|
+
}
|
|
170
143
|
/** @type {object} */
|
|
171
|
-
ruleOpts = {}
|
|
144
|
+
ruleOpts = {}
|
|
172
145
|
/** @type {array} */
|
|
173
|
-
customRulesOpts = []
|
|
146
|
+
customRulesOpts = []
|
|
174
147
|
/** @type {array} */
|
|
175
|
-
pluginRules = []
|
|
176
|
-
/** @type {ESLintLoader | undefined} */
|
|
177
|
-
#loader = undefined;
|
|
178
|
-
get loader () {
|
|
179
|
-
return this.#loader ??= this.isESLintLegacy
|
|
180
|
-
? new ESLint8Loader()
|
|
181
|
-
: new ESLint9Loader()
|
|
182
|
-
};
|
|
148
|
+
pluginRules = []
|
|
183
149
|
|
|
184
150
|
/**
|
|
185
151
|
* Initializes `cds lint` call and generates the required content
|
|
186
152
|
* object for `cds` executable
|
|
187
|
-
* @returns
|
|
153
|
+
* @returns {{
|
|
154
|
+
* help: string,
|
|
155
|
+
* options: string[],
|
|
156
|
+
* flags: unknown[],
|
|
157
|
+
* shortcuts: unknown[],
|
|
158
|
+
* }} object containing help, options, flags and shortcuts
|
|
188
159
|
*/
|
|
189
|
-
init(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.flags = flags;
|
|
197
|
-
} else {
|
|
198
|
-
console.log(`${term.error("Cannot call 'eslint -h', install and try again.")}\n`);
|
|
199
|
-
}
|
|
200
|
-
return { help, options, flags, shortcuts };
|
|
160
|
+
init() {
|
|
161
|
+
const eslintHelp = generators.genEslintHelp(this.eslintCmd)
|
|
162
|
+
const eslintFlags = generators.genEslintFlags(eslintHelp)
|
|
163
|
+
const [help, options, shortcuts, flags] = generators.genEslintShortcutsAndOpts(eslintHelp, eslintFlags)
|
|
164
|
+
this.help = help
|
|
165
|
+
this.flags = flags
|
|
166
|
+
return { help, flags, options, shortcuts }
|
|
201
167
|
}
|
|
202
168
|
|
|
203
169
|
/**
|
|
@@ -207,36 +173,33 @@ class Linter {
|
|
|
207
173
|
* @param {{[key: string]: unknown}} options options/flag passed by user
|
|
208
174
|
*/
|
|
209
175
|
async lint(args, options) {
|
|
210
|
-
//
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
this.#printHelp();
|
|
219
|
-
return
|
|
176
|
+
// user should install eslint locally
|
|
177
|
+
const { loadESLint } = await tryRequire('eslint')
|
|
178
|
+
if (!loadESLint)
|
|
179
|
+
throw 'ESLint is not installed. Please install ESLint in your project using \'npm i -D eslint\'.'
|
|
180
|
+
|
|
181
|
+
const DefaultESLint = await loadESLint()
|
|
182
|
+
if (options?.version) {
|
|
183
|
+
return this.#printVersion(DefaultESLint.version)
|
|
220
184
|
}
|
|
221
|
-
// Determine if case "local" or "global" and resolve plugin accordingly
|
|
222
|
-
await this.#getConfigFileAndSetProjectPath(args);
|
|
223
185
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
186
|
+
this.eslintCmdOpts = options
|
|
187
|
+
this.eslintCmdFileExpr = args.length ? args : ['.']
|
|
188
|
+
|
|
189
|
+
if (this.eslintCmdOpts.help)
|
|
190
|
+
return this.#printHelp()
|
|
230
191
|
|
|
192
|
+
if (await this.#getPluginPath()) {
|
|
193
|
+
await this.#overwriteRuleSeverities()
|
|
231
194
|
// Limit to CDS file extensions
|
|
232
|
-
this.#addExtensions()
|
|
195
|
+
await this.#addExtensions()
|
|
233
196
|
}
|
|
234
197
|
|
|
235
198
|
// Run ESLint with collected options
|
|
236
199
|
try {
|
|
237
|
-
await this.#runEslint(
|
|
200
|
+
await this.#runEslint(DefaultESLint)
|
|
238
201
|
} catch (err) {
|
|
239
|
-
term.error(err)
|
|
202
|
+
term.error(err)
|
|
240
203
|
}
|
|
241
204
|
}
|
|
242
205
|
|
|
@@ -244,207 +207,116 @@ class Linter {
|
|
|
244
207
|
* Prints help message
|
|
245
208
|
*/
|
|
246
209
|
#printHelp() {
|
|
247
|
-
console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`))
|
|
210
|
+
console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#printVersion(version) {
|
|
214
|
+
console.log(`Using ESLint version ${term.codes.bold}${version}${term.codes.reset}`)
|
|
248
215
|
}
|
|
249
216
|
|
|
250
|
-
#addExtensions() {
|
|
217
|
+
async #addExtensions() {
|
|
251
218
|
// Add CDS file extensions to lint
|
|
252
|
-
this.fileExtensions = this.
|
|
253
|
-
.map(
|
|
219
|
+
this.fileExtensions = (await this.#getPluginApi()).getFileExtensions()
|
|
220
|
+
.map(ext => path.extname(ext))
|
|
254
221
|
// Only lint file extensions prescribed by plugin
|
|
255
222
|
this.ignorePatterns = this.fileExtensions
|
|
256
223
|
.map(ext => `!${ext}`)
|
|
257
224
|
}
|
|
258
225
|
|
|
259
|
-
async #runEslint(
|
|
226
|
+
async #runEslint(DefaultESLint) {
|
|
260
227
|
try {
|
|
261
|
-
const eslintOpts =
|
|
262
|
-
? {
|
|
228
|
+
const eslintOpts = {
|
|
263
229
|
cwd: process.cwd(),
|
|
264
|
-
overrideConfig:
|
|
265
|
-
}
|
|
266
|
-
: {
|
|
267
|
-
cwd: currentDir,
|
|
268
|
-
overrideConfig: {...this.configContents},
|
|
230
|
+
overrideConfig: await this.#getConfigContents(),
|
|
269
231
|
}
|
|
270
232
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
eslintOpts.overrideConfig.plugins = ["@sap/eslint-plugin-cds"]
|
|
276
|
-
eslintOpts.useEslintrc = false
|
|
277
|
-
if (this.lintType === "global") {
|
|
278
|
-
eslintOpts.resolvePluginsRelativeTo = this.cdsdkPath
|
|
279
|
-
}
|
|
280
|
-
} else {
|
|
281
|
-
LOG?.("Using flat ESLint configuration");
|
|
282
|
-
const cdsPlugin = require(this.pluginPath)
|
|
283
|
-
// Exclude our own apis from plugin to avoid wrapper errors
|
|
284
|
-
delete cdsPlugin.configs.recommended?.plugins?.['@sap/cds']
|
|
285
|
-
eslintOpts.overrideConfig = [cdsPlugin.configs.recommended]
|
|
286
|
-
}
|
|
233
|
+
const cdsPlugin = require(await this.#getPluginPath())
|
|
234
|
+
// Exclude our own apis from plugin to avoid wrapper errors
|
|
235
|
+
//delete cdsPlugin.configs.recommended?.plugins?.['@sap/cds']
|
|
236
|
+
eslintOpts.overrideConfig = [cdsPlugin.configs.recommended]
|
|
287
237
|
|
|
288
|
-
if (this.customRulesOpts
|
|
238
|
+
if (this.customRulesOpts?.length) {
|
|
289
239
|
eslintOpts.rulePaths = [this.customRulesOpts]
|
|
290
240
|
}
|
|
291
241
|
|
|
292
|
-
LOG_CONFIG?.(eslintOpts)
|
|
242
|
+
LOG_CONFIG?.(eslintOpts)
|
|
293
243
|
|
|
294
244
|
if (LOG) {
|
|
295
|
-
|
|
296
|
-
|
|
245
|
+
const lintString = ['npx eslint']
|
|
297
246
|
if (this.fileExtensions) {
|
|
298
|
-
lintString
|
|
247
|
+
lintString.push(`--ext "${this.fileExtensions.join(',')}"`)
|
|
299
248
|
}
|
|
300
|
-
|
|
301
249
|
for (const [name, rule] of Object.entries(this.ruleOpts)) {
|
|
302
|
-
lintString
|
|
250
|
+
lintString.push(`--rule ${name}:${rule}`)
|
|
303
251
|
}
|
|
304
|
-
if (this.customRulesOpts
|
|
305
|
-
lintString
|
|
252
|
+
if (this.customRulesOpts?.length) {
|
|
253
|
+
lintString.push(`--rulesdir "${this.customRulesOpts}"`)
|
|
306
254
|
}
|
|
307
255
|
|
|
308
|
-
LOG(lintString)
|
|
256
|
+
LOG(lintString.join(' '))
|
|
309
257
|
}
|
|
310
258
|
|
|
311
|
-
if (
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
259
|
+
if (process.env.isTest)
|
|
260
|
+
// only log config and hypothetical lintString, do not actually run
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
/** @type {import('eslint').ESLint} */
|
|
264
|
+
const eslint = new DefaultESLint(eslintOpts)
|
|
265
|
+
const formatter = await eslint.loadFormatter('stylish')
|
|
266
|
+
|
|
267
|
+
// environment rules
|
|
268
|
+
const results = (await eslint.lintText(''))
|
|
269
|
+
.filter(result => result.messages.length)
|
|
270
|
+
.map((result) => {
|
|
271
|
+
result.filePath = path.resolve(process.cwd())
|
|
272
|
+
return result
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// lint files in current dir
|
|
276
|
+
const files = await readdir(process.cwd())
|
|
277
|
+
if (files.some(file => isfile(file))) {
|
|
278
|
+
results.push(...await eslint.lintFiles(this.eslintCmdFileExpr))
|
|
279
|
+
}
|
|
319
280
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
281
|
+
if (results?.length) {
|
|
282
|
+
console.log(formatter.format(results))
|
|
283
|
+
const { errorCount, fatalErrorCount } = countErrors(results)
|
|
284
|
+
if (fatalErrorCount) {
|
|
285
|
+
exit(FATAL_ERROR)
|
|
325
286
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
console.log(formatter.format(results));
|
|
329
|
-
const { errorCount, fatalErrorCount } = this.#countErrors(results);
|
|
330
|
-
if (fatalErrorCount > 0) {
|
|
331
|
-
exit(FATAL_ERROR);
|
|
332
|
-
} else if (errorCount > 0) {
|
|
333
|
-
exit(GENERAL_ERROR);
|
|
334
|
-
}
|
|
287
|
+
if (errorCount) {
|
|
288
|
+
exit(GENERAL_ERROR)
|
|
335
289
|
}
|
|
336
290
|
}
|
|
337
291
|
} catch (err) {
|
|
338
292
|
// Report identically to ESLint CLI
|
|
339
|
-
if (typeof err.messageTemplate ===
|
|
293
|
+
if (typeof err.messageTemplate === 'string') {
|
|
340
294
|
try {
|
|
341
|
-
const eslintBase = `${require.resolve('eslint').split('eslint')[0]}/eslint
|
|
342
|
-
const template = require(path.join(eslintBase, `messages/${err.messageTemplate}.js`))
|
|
343
|
-
console.log(template(err.messageData || {}))
|
|
295
|
+
const eslintBase = `${require.resolve('eslint').split('eslint')[0]}/eslint`
|
|
296
|
+
const template = require(path.join(eslintBase, `messages/${err.messageTemplate}.js`))
|
|
297
|
+
console.log(template(err.messageData || {}))
|
|
344
298
|
} catch {
|
|
345
299
|
// Ignore template error then fallback to use `error.stack`.
|
|
346
|
-
console.log(err.stack)
|
|
300
|
+
console.log(err.stack)
|
|
347
301
|
}
|
|
348
302
|
}
|
|
349
|
-
exit(GENERAL_ERROR)
|
|
303
|
+
exit(GENERAL_ERROR)
|
|
350
304
|
}
|
|
351
305
|
}
|
|
352
306
|
|
|
353
|
-
#countErrors(results) {
|
|
354
|
-
let errorCount = 0;
|
|
355
|
-
let fatalErrorCount = 0;
|
|
356
|
-
|
|
357
|
-
for (const result of results) {
|
|
358
|
-
errorCount += result.errorCount;
|
|
359
|
-
fatalErrorCount += result.fatalErrorCount;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return { errorCount, fatalErrorCount };
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
async #getConfigFileAndSetProjectPath(args) {
|
|
366
|
-
// Get config path
|
|
367
|
-
if (args.length > 0) {
|
|
368
|
-
const firstArg = args[0];
|
|
369
|
-
this.isFile = firstArg === "." || isfile(firstArg) || isdir(firstArg)
|
|
370
|
-
}
|
|
371
|
-
this.configPath = await this.loader.getConfigPath(process.cwd())
|
|
372
|
-
if (!this.configPath) {
|
|
373
|
-
this.configContents = this.loader.sanitizeEslintConfig({}, false, LOG)
|
|
374
|
-
} else {
|
|
375
|
-
const configContents = this.loader.readEslintConfig(this.configPath)
|
|
376
|
-
this.configContents = configContents;
|
|
377
|
-
if (this.isESLintLegacy) {
|
|
378
|
-
this.configContents = await io.sanitizeEslintConfig(configContents, false, LOG);
|
|
379
|
-
if (this.configContents) {
|
|
380
|
-
if ("extends" in this.configContents) {
|
|
381
|
-
this.extendsPlugin = checks.hasEslintConfigContent(
|
|
382
|
-
this.configContents,
|
|
383
|
-
"extends"
|
|
384
|
-
);
|
|
385
|
-
if (this.extendsPlugin) {
|
|
386
|
-
this.lintType = "local";
|
|
387
|
-
try {
|
|
388
|
-
this.pluginPath = require.resolve("@sap/eslint-plugin-cds", {
|
|
389
|
-
paths: [path.dirname(this.configPath)],
|
|
390
|
-
});
|
|
391
|
-
this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
|
|
392
|
-
} catch {
|
|
393
|
-
// CLI will report (no locally installed plugin)
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
const cdsConfig = Array.isArray(configContents)
|
|
400
|
-
? configContents?.find(c => c.files?.includes("*.cds")) ?? {}
|
|
401
|
-
: configContents
|
|
402
|
-
this.configContents = cdsConfig.files
|
|
403
|
-
? {
|
|
404
|
-
...cdsConfig.plugins.configs?.recommended,
|
|
405
|
-
languageOptions: cdsConfig.languageOptions,
|
|
406
|
-
files: cdsConfig.files,
|
|
407
|
-
rules: cdsConfig.rules,
|
|
408
|
-
}
|
|
409
|
-
: {};
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
if (!this.pluginPath) {
|
|
413
|
-
try {
|
|
414
|
-
this.pluginPath = require.resolve("@sap/eslint-plugin-cds", {
|
|
415
|
-
paths: [this.cdsdkPath],
|
|
416
|
-
});
|
|
417
|
-
this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
|
|
418
|
-
} catch {
|
|
419
|
-
// CLI will report (no globally installed plugin)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
LOG?.(`Lint type: ${this.lintType}`);
|
|
423
|
-
LOG?.(`ESLint CDS plugin resolved: ${this.pluginPath}`);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
307
|
async #overwriteRuleSeverities() {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
configContents = await io.readEslintConfigLegacy(this.configPath);
|
|
430
|
-
} else {
|
|
431
|
-
configContents = this.loader.readEslintConfig(await this.loader.getConfigPath(this.cdsdkPath))
|
|
432
|
-
}
|
|
433
|
-
const rules = configContents?.rules ?? {};
|
|
434
|
-
// Allow recommended plugin rules in cds-dk to be overwritten
|
|
308
|
+
const rules = this.#getConfigContents().rules ?? {}
|
|
309
|
+
// Allow recommended plugin rules to be overwritten
|
|
435
310
|
// by user by adding rule to cmd line (because of precedence)
|
|
436
|
-
const pluginRules = require(this
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.ruleOpts[name] = rule;
|
|
443
|
-
this.pluginRules = pluginRules;
|
|
444
|
-
}
|
|
311
|
+
const pluginRules = require(await this.#getPluginPath()).configs.recommended.rules ?? {}
|
|
312
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
313
|
+
if (rule && rule !== pluginRules[name] || isfile(path.relative('.', '.eslint', 'rules', name))) {
|
|
314
|
+
pluginRules[name] = rule
|
|
315
|
+
this.ruleOpts[name] = rule
|
|
316
|
+
this.pluginRules = pluginRules
|
|
445
317
|
}
|
|
446
318
|
}
|
|
447
319
|
}
|
|
448
320
|
}
|
|
449
321
|
|
|
450
|
-
module.exports = Linter
|
|
322
|
+
module.exports = Linter
|