@sap/cds-compiler
Version:
CDS (Core Data Services) compiler and backends
139 lines (111 loc) • 4.05 kB
JavaScript
// Remove whitespace where the compiler does not expect it and will
// emit an error in the next major version. (Remark: not necessarily)
//
// This script removes whitespace where necessary.
// It does not remove comments, even though they count as whitespace.
//
// Example CDS:
// entity P as projection on E { s. * };
// will become:
// entity P as projection on E { s.* };
//
// Usage:
// ./cds_remove_invalid_whitespace.js my_file.cds
//
// If you want to update all files in a directory, you can use
// this shell script:
// find . -type f -iname '*.cds' -exec ./cds_remove_invalid_whitespace.js {} \;
//
// Note that you need to update the path to this script in the commands above.
//
/* eslint no-process-exit: 0, no-console: 0 */
;
const parsers = require('../lib/parsers');
const { createMessageFunctions } = require('../lib/base/messages');
const fs = require('fs');
const path = require('path');
const cliArgs = process.argv.slice(2);
const filepath = cliArgs[0];
if (filepath === '--help' || filepath === '-h')
exitError();
if (cliArgs.length !== 1)
exitError(`Expected exactly one argument, ${ cliArgs.length } given`);
if (!filepath)
exitError('Expected non-empty filepath as argument!');
// Do not use allow-list approach.
// There may be CDS files with other extensions than `.cds`.
if (filepath.endsWith('.csn') || filepath.endsWith('.json'))
exitError('Only CDS files can be passed! Found CSN file!');
const sourceStr = fs.readFileSync(filepath, 'utf-8');
const newSourceStr = modernizeWhitespace(sourceStr, filepath);
if (newSourceStr !== sourceStr)
fs.writeFileSync(filepath, newSourceStr);
process.exit(0); // success
// --------------------------------------------------------
function modernizeWhitespace( source, filename ) {
const options = { messages: [], attachTokens: true };
const messageFunctions = createMessageFunctions( options, 'parse', null );
// parseCdl does not throw on CompilationError, so
// we do not need a try...catch block.
const ast = parsers.parseCdl(source, filename, options, messageFunctions);
// To avoid spam, only report errors.
// Users should use the compiler to get all messages.
const errors = options.messages
.filter(msg => (msg.severity === 'Error' && msg.messageId !== 'syntax-invalid-space'));
if (errors.length > 0) {
errors.forEach((msg) => {
console.error(msg.toString());
});
console.error(`Found ${ errors.length } errors! \n`);
exitError('The CDS parser emitted errors. Fix them first and try again.');
}
if (!ast.artifacts)
return source;
let currentOffset = 0;
const { tokens } = ast.tokenStream;
for (let i = 0; i < tokens.length; ++i) {
const token = tokens[i];
const next = tokens[i + 1];
if (next && token.type === ast.tokenStream.DOTbeforeBRACE &&
(next.type === ast.tokenStream.ASTERISK || next.type === ast.tokenStream.BRACE))
updateWhitespace(token, next);
}
return source;
// -----------------------------------------------
function updateWhitespace( dot, braceOrAsterisk ) {
if (dot.stop === braceOrAsterisk.start)
return;
const from = dot.stop + currentOffset + 1; // end points at the position *before* the character
const to = braceOrAsterisk.start + currentOffset;
source = replaceSliceInSource(source, from, to, '');
currentOffset -= to - from;
}
}
/**
* Replaces a given span with @p replaceWith
*
* @param {string} source
* @param {number} startIndex
* @param {number} endIndex
* @param {string} replaceWith
* @return {string}
*/
function replaceSliceInSource( source, startIndex, endIndex, replaceWith ) {
return source.substring(0, startIndex) +
replaceWith +
source.substring(endIndex);
}
/**
* @param {string} [msg]
*/
function exitError( msg ) {
if (msg)
console.error(msg);
usage();
process.exit(1);
}
function usage() {
console.error('');
console.error(`usage: ${ path.basename(process.argv[1]) } <filename>`);
}