UNPKG

@sap/eslint-plugin-cds

Version:

ESLint plugin including recommended SAP Cloud Application Programming model and environment rules

184 lines (170 loc) 4.6 kB
'use strict' const { RULE_CATEGORIES } = require('../constants') // Check that Java keywords are not used as identifiers unless they have // a Java-specific annotation that renames/ignores them. This avoids issues // later on in code-generation of CAP Java classes. // Test Java code via godbolt.org: https://godbolt.org/z/1c5s49qjo const { splitDefName } = require('../utils/rules') // There is also `@cds.java.this.name`, which is not relevant for this check. const ANNO_JAVA_NAME = '@cds.java.name' const ANNO_JAVA_IGNORE = '@cds.java.ignore' // CSN kinds that are relevant for code generation with possible keyword // conflicts. For example, types are not relevant, because they use // PascalCase, i.e. it can never be a keyword conflict, since all keywords // are lowercase. const relevantKinds = [ 'element', 'param', 'action', 'function', ] module.exports = { meta: { schema: [{/* to avoid deprecation warning for ESLint 9 */}], docs: { category: RULE_CATEGORIES.model, description: 'Reject reserved Java keywords as CDS identifiers.', url: 'https://cap.cloud.sap/docs/tools/cds-lint/rules/no-java-keywords', }, type: 'problem', model: 'inferred', messages: { keywordJava: `'{{name}}' is a reserved keyword in Java. Use '@cds.java.name' to override the name for Java code generation.`, }, }, create (context) { const rootPath = context.getRootPath() if (!rootPath) return return function checkForJavaKeywords(){ const model = context.getModel() if (!model) return for (const name in model.definitions) checkDefinition(model.definitions[name]) } function checkDefinition(def) { checkNameIsNotReserved(def) if (def.elements) { for (const name in def.elements) checkDefinition(def.elements[name]) } if (def.actions) { for (const name in def.actions) checkDefinition(def.actions[name]) } if (def.kind === 'action' || def.kind === 'function') { for (const name in def.params) checkDefinition(def.params[name]) } } function checkNameIsNotReserved(artifact) { if (!artifact.$location?.file || !relevantKinds.includes(artifact.kind)) return if (artifact[ANNO_JAVA_IGNORE]) return // ignored; no Java code generated if (artifact[ANNO_JAVA_NAME]) return // explicitly renamed; assume the user uses a valid name const name = artifact.is('element') ? artifact.name : splitDefName(artifact).name if (isValueReservedJavaKeyword(name)) { context.report({ messageId: 'keywordJava', data: { name }, node: context.getNode(artifact), file: artifact.$location.file, }) } } } } // List from https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html // Also available at https://github.com/openjdk/jdk/blob/f92c60e1a9968620cbc92b52aa546b57c09da487/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java#L651 // though that list includes fewer items. const JAVA_RESERVED = [ '_', 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float', 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'try', 'void', 'volatile', 'while', // literals 'true', 'false', 'null', ] /** * Check if the given value is a reserved keyword. * * @param {any} name * @returns {boolean} */ function isValueReservedJavaKeyword(name) { if (!name || typeof name !== 'string') return false const normalized = identifierForJava(name) return JAVA_RESERVED.includes(normalized) } /** * Returns the check-relevant identifier for Java. * CAP Java does not use lowercase for the full identifier, but instead * uses lowerCamelCase, i.e. it is enough to change the first character * of the identifier. * * @param {string} name * @returns {string} */ function identifierForJava(name) { if (!name) return name const firstChar = name.charAt(0) if (firstChar === firstChar.toLowerCase()) return name return `${firstChar.toLowerCase()}${name.slice(1)}` }