@sap/eslint-plugin-cds 2.6.7 → 3.0.2

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 CHANGED
@@ -6,6 +6,51 @@ This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
8
8
 
9
+ ## [3.0.2] - 2024-04-29
10
+
11
+ ### Fixed
12
+
13
+ - Internal parser call now handles `ESLint` version 8 and 9
14
+
15
+ ### Changed
16
+
17
+ - requires `ESLint` version 8 or above
18
+
19
+ ## [3.0.1] - 2024-04-25
20
+
21
+ ### Fixed
22
+
23
+ - Add namespace `@sap/cds` to plugin configuration
24
+
25
+ ## [3.0.0] - 2024-04-23
26
+
27
+ ### Added
28
+
29
+ - Support ESLint flat configurations (`eslint@v9`) and make them available as *recommended*, *all*.
30
+
31
+ ### Changed
32
+
33
+ - Plugin configurations (*recommended*, *all*) for `eslint@<v9` are now available with the `-legacy` suffix.
34
+
35
+ ### Fixed
36
+
37
+ - In _latest-cds-version_, get output from `npm outdated` on exit code 1.
38
+
39
+ ## [2.7.0] - 2024-04-12
40
+
41
+ ### Added
42
+
43
+ - Add `getRootPath()` method to `context` object to get the project rootPath.
44
+
45
+ ### Changed
46
+
47
+ - Rule option "show" now allows inferred rules to rerun/recompile instead of just running once (as is the CLI behavior).
48
+ - Removed `min-node-version` rule, as it is now covered by the cds CLI.
49
+
50
+ ### Fixed
51
+
52
+ - In _no-db-keywords_, use `getRootPath()` instead of dirname, as wrong paths lead to missing db entries, disabling the rule.
53
+
9
54
  ## [2.6.7] - 2024-03-11
10
55
 
11
56
  ### Fixed
package/lib/conf/all.js CHANGED
@@ -10,7 +10,6 @@ module.exports = {
10
10
  '@sap/cds/auth-valid-restrict-to': 2,
11
11
  '@sap/cds/auth-valid-restrict-where': 2,
12
12
  '@sap/cds/latest-cds-version': 2,
13
- '@sap/cds/min-node-version': 2,
14
13
  '@sap/cds/no-db-keywords': 2,
15
14
  '@sap/cds/no-dollar-prefixed-names': 2,
16
15
  '@sap/cds/no-join-on-draft': 2,
package/lib/conf/index.js CHANGED
@@ -1,22 +1,44 @@
1
+ const path = require('path')
1
2
  const { FILES, GLOBALS, PLUGIN_NAME } = require('../constants')
2
3
  const { parserPath } = require('../api')
3
4
 
4
- function _createConfig (config) {
5
+ function _createConfig (plugin, configName, legacy = false) {
6
+ const config = require(path.join(__dirname, configName))
7
+ if (legacy) {
8
+ return {
9
+ root: true,
10
+ globals: GLOBALS,
11
+ plugins: [PLUGIN_NAME],
12
+ overrides: [
13
+ {
14
+ files: FILES,
15
+ parser: parserPath
16
+ }
17
+ ],
18
+ rules: config
19
+ }
20
+ }
5
21
  return {
6
- root: true,
7
- globals: GLOBALS,
8
- plugins: [PLUGIN_NAME],
9
- overrides: [
10
- {
11
- files: FILES,
12
- parser: parserPath
13
- }
14
- ],
22
+ languageOptions: {
23
+ globals: GLOBALS,
24
+ parser: require(parserPath)
25
+ },
26
+ plugins: {
27
+ '@sap/cds': plugin
28
+ },
29
+ files: FILES.map(file => file.replace('*.', '**/*.')),
15
30
  rules: config
16
31
  }
17
32
  }
18
33
 
19
- module.exports = {
20
- all: _createConfig(require('./all')),
21
- recommended: _createConfig(require('./recommended'))
34
+ // Export both eslintrc and flat configs to ensure backwards compatibility (<eslint@v9):
35
+ // https://eslint.org/docs/latest/extend/plugin-migration-flat-config#backwards-compatibility
36
+ module.exports = function (plugin) {
37
+ return {
38
+ all: _createConfig(plugin, 'all'),
39
+ recommended: _createConfig(plugin, 'recommended'),
40
+ // Legacy configs (for backwards compatibility)
41
+ 'all-legacy': _createConfig(plugin, 'all', true),
42
+ 'recommended-legacy': _createConfig(plugin, 'recommended', true)
43
+ }
22
44
  }
@@ -9,7 +9,6 @@ module.exports = {
9
9
  '@sap/cds/auth-valid-restrict-keys': 1,
10
10
  '@sap/cds/auth-valid-restrict-to': 1,
11
11
  '@sap/cds/auth-valid-restrict-where': 1,
12
- '@sap/cds/min-node-version': 2,
13
12
  '@sap/cds/no-db-keywords': 1,
14
13
  '@sap/cds/no-dollar-prefixed-names': 1,
15
14
  '@sap/cds/no-join-on-draft': 1,
package/lib/index.js CHANGED
@@ -15,15 +15,30 @@
15
15
  * - Expose any 'rules' for use in ESLint
16
16
  */
17
17
 
18
+ const path = require('node:path')
19
+
18
20
  const api = require('./api')
21
+ const getConfigs = require('./conf')
19
22
  const rules = Object.assign(
20
23
  {},
21
24
  ...Object.entries(require('./rules')).map(([k, v]) => ({ [k]: v() }))
22
25
  )
23
- const configs = require('./conf')
24
26
 
25
- module.exports = {
26
- configs,
27
+ const packageJson = require(path.join(__dirname, '../package.json'))
28
+
29
+ const plugin = {
30
+ meta: {
31
+ name: packageJson.name,
32
+ version: packageJson.version
33
+ },
34
+ configs: {},
27
35
  rules,
28
36
  ...api
29
37
  }
38
+
39
+ // Assign configs here so we can reference `plugin`
40
+ Object.assign(plugin.configs, getConfigs(plugin))
41
+
42
+ // Use commonJS entry point to ensure backwards compatibility (<eslint@v9):
43
+ // https://eslint.org/docs/latest/extend/plugin-migration-flat-config#backwards-compatibility
44
+ module.exports = plugin
package/lib/parser.js CHANGED
@@ -28,7 +28,7 @@ module.exports = {
28
28
  const messages = []
29
29
  try {
30
30
  compiledModel = cds.parse(code)
31
- } catch (err) {
31
+ } catch (_err) {
32
32
  // Do nothing
33
33
  }
34
34
  if (compiledModel) {
@@ -57,7 +57,7 @@ module.exports = {
57
57
  if (whereValues && typeof whereValues === 'string') {
58
58
  try {
59
59
  cds.parse.expr(entry.where)
60
- } catch (err) {
60
+ } catch (_err) {
61
61
  context.report({
62
62
  message: 'Invalid `where` expression, CDS compilation failed.',
63
63
  node,
@@ -11,7 +11,6 @@ const rules = {
11
11
  'auth-valid-restrict-to': () => createRule(require('./auth-valid-restrict-to')),
12
12
  'auth-valid-restrict-where': () => createRule(require('./auth-valid-restrict-where')),
13
13
  'latest-cds-version': () => createRule(require('./latest-cds-version')),
14
- 'min-node-version': () => createRule(require('./min-node-version')),
15
14
  'no-db-keywords': () => createRule(require('./no-db-keywords')),
16
15
  'no-dollar-prefixed-names': () => createRule(require('./no-dollar-prefixed-names')),
17
16
  'no-join-on-draft': () => createRule(require('./no-join-on-draft')),
@@ -22,12 +22,14 @@ module.exports = {
22
22
  let cdsVersions
23
23
  let e = context.getEnvironment()
24
24
  if (!e) {
25
- e = cp
26
- .execSync('npm outdated @sap/cds --json', {
25
+ try {
26
+ cp.execSync('npm outdated @sap/cds --json', {
27
27
  cwd: process.cwd(),
28
28
  stdio: 'pipe'
29
- })
30
- .toString()
29
+ }).toString()
30
+ } catch (err) {
31
+ e = JSON.parse(err.stdout.toString())
32
+ }
31
33
  }
32
34
  if (e && e['@sap/cds']) {
33
35
  cdsVersions = e['@sap/cds']
@@ -1,5 +1,3 @@
1
- const { dirname } = require('path')
2
-
3
1
  const cds = require('@sap/cds')
4
2
 
5
3
  module.exports = {
@@ -13,9 +11,9 @@ module.exports = {
13
11
  model: 'inferred'
14
12
  },
15
13
  create (context) {
16
- let dir = context.getFilename()
17
- dir = dirname(dir)
18
- const { requires } = cds.env.for('cds', dir)
14
+ const rootPath = context.getRootPath()
15
+ if (!rootPath) return
16
+ const { requires } = cds.env.for('cds', rootPath)
19
17
  if (requires.db?.kind !== 'sqlite') return
20
18
 
21
19
  return {
@@ -49,7 +49,7 @@ module.exports = (spec) => {
49
49
  }
50
50
  const cdscontext = extendContext(node, context, meta)
51
51
  Cache.set('context', cdscontext)
52
- const { isTest, isValidFile, doEnvironmentChecks, doRootModelChecks } = checkEntryCriteria(meta, cdscontext)
52
+ const { isTest, isValidFile, doEnvironmentChecks, doRootModelChecks, showInEditor } = checkEntryCriteria(meta, cdscontext)
53
53
  switch (meta.model) {
54
54
  case 'none':
55
55
  if (doEnvironmentChecks) {
@@ -62,10 +62,17 @@ module.exports = (spec) => {
62
62
  break
63
63
 
64
64
  case 'inferred':
65
- if (isValidFile && doRootModelChecks) {
66
- if (isTest || !Cache.has(`rule:${cdscontext.id}:${Cache.get('rootpath')}`)) {
65
+ if (isValidFile && (doRootModelChecks)) {
66
+ if (showInEditor) {
67
+ Cache.remove(`model:${Cache.get('rootpath')}`)
68
+ Cache.remove(`rule:${cdscontext.id}:${Cache.get('rootpath')}`)
69
+ Cache.remove(`report:${context.getFilename()}:${context.id}`)
70
+ }
71
+ if (isTest || showInEditor || Cache.has(`rule:${cdscontext.id}:${Cache.get('rootpath')}`)) {
67
72
  LOG && LOG(` Model: "${meta.model}" Rule: ${context.id}`)
68
- Cache.set(`rule:${cdscontext.id}:${Cache.get('rootpath')}`, 'done')
73
+ if (!showInEditor) {
74
+ Cache.set(`rule:${cdscontext.id}:${Cache.get('rootpath')}`, 'done')
75
+ }
69
76
  createReport(node, cdscontext, meta, create)
70
77
  } else {
71
78
  if (Cache.has(`report:${context.getFilename()}:${context.id}`)) {
@@ -107,13 +114,14 @@ function isRunningWithESLint () {
107
114
 
108
115
  function checkEntryCriteria (meta, cdscontext) {
109
116
  const isTest = Cache.has('test')
117
+ const showInEditor = cdscontext.options.includes('show')
110
118
  const hasProjectRoots = Cache.has(`roots:${Cache.get('rootpath')}`)
111
119
  const isValidFile = isConfiguredFileType(cdscontext.getFilename(), 'FILES')
112
- const doRootModelChecks = isTest || (hasProjectRoots && (isRunningWithCDSLint() || isRunningWithESLint() || cdscontext.options.includes('show')))
113
- // Also lint empty folders (i.e. lintText "" API)
120
+ const doRootModelChecks = isTest || (hasProjectRoots && (isRunningWithCDSLint() || isRunningWithESLint()) || showInEditor)
121
+ // Lint all env rules independent of any parsed file (i.e. 'cds lint' uses the lintText "" API)
114
122
  const doEnvironmentChecks =
115
- isTest || (hasProjectRoots && isRunningWithCDSLint() && cdscontext.getSourceCode().lines[0] === '')
116
- return { isTest, isValidFile, doRootModelChecks, doEnvironmentChecks }
123
+ isTest || (isRunningWithCDSLint() && cdscontext.getFilename() === '<text>')
124
+ return { isTest, isValidFile, doRootModelChecks, doEnvironmentChecks, showInEditor }
117
125
  }
118
126
 
119
127
  function setMetaDefaults (meta) {
@@ -158,8 +166,8 @@ function createReport (node, cdscontext, meta, create) {
158
166
  const isValidLocation = (meta.model === 'parsed' && d.$location) ||
159
167
  (meta.model === 'inferred' && d.$location?.file)
160
168
  Object.entries(handlers)
161
- .filter(([type, lazy]) => d.is(type) && isValidLocation)
162
- .forEach(([lazy, handler]) => {
169
+ .filter(([type, _lazy]) => d.is(type) && isValidLocation)
170
+ .forEach(([_lazy, handler]) => {
163
171
  try {
164
172
  handler(d)
165
173
  } catch (err) {
@@ -230,6 +238,7 @@ function extendContext (node, context, meta) {
230
238
  }
231
239
  cdscontext.getLocation = parserServices.getLocation
232
240
  cdscontext.getNode = Object.keys(parserServices).length > 0 ? parserServices.getNode : () => node
241
+ cdscontext.getRootPath = () => Cache.get('rootpath')
233
242
  return cdscontext
234
243
  }
235
244
 
@@ -166,7 +166,7 @@ function getPackageVersion (registry) {
166
166
  stdio: 'pipe'
167
167
  })
168
168
  .toString()
169
- } catch (err) {
169
+ } catch (_err) {
170
170
  LOG?.(`Failed to connect to ${registry} - check your connection and try again.`)
171
171
  exit(0)
172
172
  }
@@ -231,7 +231,7 @@ function getRules (docsPath, rulePath, testPath, versionRequired = '0.0.0', rele
231
231
  }
232
232
  try {
233
233
  mdRule = getRuleExamples(rule, ruleTestPath, testPath, rulesEntry)
234
- } catch (err) {
234
+ } catch (_err) {
235
235
  // Just continue
236
236
  }
237
237
  mdRuleContents = ''
@@ -9,16 +9,23 @@
9
9
  const fs = require('fs')
10
10
  const path = require('path')
11
11
 
12
- module.exports = (currentDir = '.') => {
13
- const configFiles = [
14
- '.eslintrc.js',
15
- '.eslintrc.cjs',
16
- '.eslintrc.yaml',
17
- '.eslintrc.yml',
18
- '.eslintrc.json',
19
- '.eslintrc',
20
- 'package.json'
12
+ module.exports = (currentDir = '.', legacy=false) => {
13
+ let configFiles = [
14
+ 'eslint.config.js',
15
+ 'eslint.config.cjs',
16
+ 'eslint.config.mjs'
21
17
  ]
18
+ if (legacy) {
19
+ configFiles = [
20
+ '.eslintrc.js',
21
+ '.eslintrc.cjs',
22
+ '.eslintrc.yaml',
23
+ '.eslintrc.yml',
24
+ '.eslintrc.json',
25
+ '.eslintrc',
26
+ 'package.json',
27
+ ]
28
+ }
22
29
  let configDir = path.resolve(currentDir)
23
30
  while (configDir !== path.resolve(configDir, '..')) {
24
31
  for (const configFile of configFiles) {
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
 
4
- const { RuleTester } = require('eslint')
4
+ const { Linter, RuleTester } = require('eslint')
5
5
  const Cache = require('./Cache')
6
6
  const createRule = require('./createRule')
7
7
  const isConfiguredFileType = require('./isConfiguredFileType')
@@ -17,13 +17,13 @@ const { compileModelFromDict } = require('../parser')
17
17
  * @returns RuleTester results
18
18
  */
19
19
  module.exports = (options) => {
20
- let parser
20
+ let parserPath
21
21
  let rule = {}
22
22
  Cache.set('rules', require(path.join(__dirname, '../rules')))
23
23
  const rulename = path.basename(options.root)
24
24
  if (options.root.startsWith(path.resolve(__dirname, '../..'))) {
25
25
  // For plugin's internal tests, resolve parser from here
26
- parser = require.resolve('../parser')
26
+ parserPath = require.resolve('../parser')
27
27
  const pluginPath = path.join(path.dirname(options.root), '../..')
28
28
  rule = createRule(require(`../rules/${path.basename(options.root)}`))
29
29
  Cache.set('pluginpath', pluginPath)
@@ -32,15 +32,22 @@ module.exports = (options) => {
32
32
  const resolvedPlugin = require.resolve('@sap/eslint-plugin-cds', {
33
33
  paths: [options.root]
34
34
  })
35
- parser = path.join(path.dirname(resolvedPlugin), 'parser')
35
+ parserPath = path.join(path.dirname(resolvedPlugin), 'parser')
36
36
  rule = require(path.join(options.root, `../../rules/${path.basename(options.root)}`))
37
37
  const pluginPath = path.join(path.dirname(options.root), '../../..')
38
38
  Cache.set('pluginpath', pluginPath)
39
39
  }
40
40
  let tester = new RuleTester({})
41
- if (parser) {
42
- tester = new RuleTester({ parser })
41
+ if (parserPath) {
42
+ let options
43
+ if (Number(Linter.version.split('.')[0]) >= 9) {
44
+ options = { languageOptions: { parser: require(parserPath) } }
45
+ } else {
46
+ options = { parser: parserPath }
47
+ }
48
+ tester = new RuleTester(options)
43
49
  }
50
+
44
51
  const testerCases = {};
45
52
  ['valid', 'invalid'].forEach((type) => {
46
53
  const filePath = path.join(options.root, `${type}/${options.filename}`)
@@ -79,7 +86,7 @@ module.exports = (options) => {
79
86
  /**
80
87
  * Creates a model for ESLint unit tests
81
88
  */
82
- function _initModelRuleTester (filePath, flavor) {
89
+ function _initModelRuleTester(filePath, flavor) {
83
90
  Cache.set('test', true)
84
91
  const rootPath = path.dirname(filePath)
85
92
  Cache.set('rootpath', rootPath)
@@ -101,7 +108,7 @@ function _initModelRuleTester (filePath, flavor) {
101
108
  * @param files
102
109
  * @returns dictFiles
103
110
  */
104
- function _getDictFiles (input, files) {
111
+ function _getDictFiles(input, files) {
105
112
  let dictFiles = {}
106
113
  if (Cache.has(`dictfiles:${input}`)) {
107
114
  dictFiles = Cache.get(`dictfiles:${input}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/eslint-plugin-cds",
3
- "version": "2.6.7",
3
+ "version": "3.0.2",
4
4
  "description": "ESLint plugin including recommended SAP Cloud Application Programming model and environment rules",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -23,14 +23,8 @@
23
23
  "@sap/cds": ">=5.6.0",
24
24
  "semver": "^7.3.4"
25
25
  },
26
- "eslintConfig": {
27
- "extends": [
28
- "eslint:recommended",
29
- "standard"
30
- ]
31
- },
32
26
  "peerDependencies": {
33
- "eslint": ">=7"
27
+ "eslint": ">=8"
34
28
  },
35
29
  "engines": {
36
30
  "node": ">=18"
@@ -1,48 +0,0 @@
1
- const path = require('path')
2
- const semver = require('semver')
3
-
4
- module.exports = {
5
- meta: {
6
- schema: [{/* to avoid deprecation warning for ESLint 9 */}],
7
- docs: {
8
- description: 'Checks whether the minimum Node.js version required by `@sap/cds` is achieved.'
9
- },
10
- severity: 'off',
11
- type: 'problem',
12
- model: 'none'
13
- },
14
- create: function (context) {
15
- return checkMinNodeVersion
16
-
17
- function checkMinNodeVersion () {
18
- const e = context.getEnvironment()
19
- let nodeVersion, nodeVersionCDS
20
- if (!e) {
21
- // Get current and required node versions
22
- try {
23
- const CDSPath = require.resolve('@sap/cds/package.json', {
24
- paths: [path.dirname('.')]
25
- })
26
- const jsonCDS = require(CDSPath)
27
- nodeVersion = process.version
28
- nodeVersionCDS = jsonCDS.engines.node
29
- } catch (err) {
30
- // Do not throw
31
- }
32
- } else {
33
- nodeVersion = e.nodeVersion
34
- nodeVersionCDS = e.nodeVersionCDS
35
- }
36
- if (
37
- nodeVersion &&
38
- nodeVersionCDS &&
39
- !semver.satisfies(nodeVersion, nodeVersionCDS, { loose: true })
40
- ) {
41
- context.report({
42
- message: `CDS minimum node version of ${nodeVersionCDS} required, found ${nodeVersion}!`,
43
- node: context.getNode()
44
- })
45
- }
46
- }
47
- }
48
- }