Skip to content

Commit

Permalink
Release August 2024 (#1230)
Browse files Browse the repository at this point in the history
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: René Jeglinsky <[email protected]>
Co-authored-by: Christian Georgi <[email protected]>
Co-authored-by: Jörg Mann <[email protected]>
Co-authored-by: Johannes Vogel <[email protected]>
Co-authored-by: sjvans <[email protected]>
Co-authored-by: Heiko Witteborg <[email protected]>
Co-authored-by: ecklie <[email protected]>
Co-authored-by: Andre Meyering <[email protected]>
Co-authored-by: Dr. David A. Kunz <[email protected]>
Co-authored-by: Marc Becker <[email protected]>
Co-authored-by: hjboth <[email protected]>
Co-authored-by: Steffen Weinstock <[email protected]>
Co-authored-by: Steffen Waldmann <[email protected]>
Co-authored-by: Adrian Görler <[email protected]>
Co-authored-by: Arley Triana Morin <[email protected]>
Co-authored-by: Daniel Hutzel <[email protected]>
Co-authored-by: rashmiangadi11 <[email protected]>
Co-authored-by: Christian Georgi <[email protected]>
Co-authored-by: Lothar Bender <[email protected]>
Co-authored-by: Markus Ofterdinger <[email protected]>
Co-authored-by: Vladimir <[email protected]>
Co-authored-by: DJ Adams <[email protected]>
Co-authored-by: Daniel O'Grady <[email protected]>
Co-authored-by: mariayord <[email protected]>
Co-authored-by: Markus Haug <[email protected]>
Co-authored-by: Robin <[email protected]>
Co-authored-by: BraunMatthias <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Bonk <[email protected]>
Co-authored-by: Matthias Schur <[email protected]>
Co-authored-by: Stefan Henke <[email protected]>
Co-authored-by: Thomas Bonk <[email protected]>
Co-authored-by: Gopal Anand <[email protected]>
Co-authored-by: Patrice Bender <[email protected]>
Co-authored-by: Olena <[email protected]>
Co-authored-by: D070615 <[email protected]>
Co-authored-by: Marten Schiwek <[email protected]>
Co-authored-by: simonoswald <[email protected]>
Co-authored-by: RoshniNaveenaS <[email protected]>
Co-authored-by: Matthias Kuhr <[email protected]>
Co-authored-by: Oliver Klemenz <[email protected]>
Co-authored-by: Samuel Brucksch <[email protected]>
Co-authored-by: Preetam Kajal Rout <[email protected]>
Co-authored-by: Marcel Schwarz <[email protected]>
Co-authored-by: Daniel Schlachter <[email protected]>
Co-authored-by: Gregor Wolf <[email protected]>
Co-authored-by: Andrei Vishnevsky <[email protected]>
Co-authored-by: Evgeny Andreev <[email protected]>
  • Loading branch information
Show file tree
Hide file tree
Showing 55 changed files with 1,514 additions and 665 deletions.
2 changes: 1 addition & 1 deletion .github/cds-snippet-checker/check-cds-snippets.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ function* extractSnippets(section) {
// Code snippets may have a configuration in form of an HTML comment.
// When a cds-mode comment exists, we ignore the language.
if (snippets[1]) {
const modeRegEx = /cds-mode: ([^,]+)$/;
const modeRegEx = /cds-mode: ([^,;]+)/;
const result = modeRegEx.exec(snippets[1]);
if (result && result[1])
mode = validateMode(result[1].trim());
Expand Down
1 change: 1 addition & 0 deletions .github/java-snippet-checker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock.json
308 changes: 308 additions & 0 deletions .github/java-snippet-checker/check-java-snippets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
#!/usr/bin/env node

// Java Snippet Checker
// ===================
//
// Similar to the CDS snippet checker, check Java snippets for syntax errors.
// We use the "java-parser" NPM package for that.
// All code-blocks are extracted. If they were set to `java`, we extract the
// snippet and parse it.
//
// In case of errors, we try to wrap the snippet and parse it again.
//
// - First try to parser it again with a class surrounding the snippet.
// - If that fails, try the same with a method.
// - If that fails, mark snippet as invalid.
//
// Also, we run a few pre-processing steps and use heuristics:
// We remove `...` markers and `imports`, etc.
//
// You can disable checking of a snippet by prepending a `<!-- mode: ignore -->`
// comment right before the snippet.
//
// TODO:
// - [ ] combine code with the CDS snippet checker.

'use strict';

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parse as parseJava } from 'java-parser';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const projectDir = path.resolve(__dirname, '../..');
const verbose = process.argv[2] === '--verbose';

// Get base directories
const excludedDirs = [
'.git',
'.github',
'node_modules',
'.reuse',
'.vitepress',
'.idea',
'.vscode',
'.devcontainer',
];
const baseDirs = fs.readdirSync(projectDir)
.filter(file => fs.statSync(path.join(projectDir, file)).isDirectory() && !excludedDirs.includes(file))
.map(file => path.join(projectDir, file));

const JAVA_MODE_SYNTAX = 'syntax';
const JAVA_MODE_IGNORE = 'ignore';
const javaModes = [
JAVA_MODE_SYNTAX,
JAVA_MODE_IGNORE,
];

let counter = 0;
let hasAnySnippetErrors = false;

// Logging should always go to stderr. Have some convenience functions to minimize verbosity.
const log = (...args) => { console.error(...args); };
const error = (...args) => { console.error(...args); };
const debug = (...args) => { verbose && console.error(...args); };

for (const dir of baseDirs) {
const files = getFilesInDirectory(dir, /[.]md/);
log(`Checking ${files.length} markdown documents in ${path.relative(projectDir, dir)}`);

for (const snippet of extractSnippetsFromFiles(files)) {
++counter;

if (snippet.mode === JAVA_MODE_IGNORE)
continue;

snippet.original = snippet.content;
snippet.content = prepareSnippet(snippet.content);

const variations = [
{ content: snippet.content, error: null },
{ content: snippetAsMethod(snippet.content), error: null },
{ content: snippetAsCode(snippet.content), error: null },
];

// We assume that the snippet has an error.
// If any of the variations _passes_, then the snippet is ok.
let snippetHasError = true;
for (const variation of variations) {
variation.error = compileSnippet(variation.content);
if (!variation.error) {
snippetHasError = false;
break; // success
}
}

if (snippetHasError) {
hasAnySnippetErrors = true;
printErrorForSnippet(snippet, variations);

} else if (verbose) {
log(`Snippet ${counter}`);
log(snippet.content);
}
}

log('');
}

log(`Checked ${counter} snippets.`);

if (hasAnySnippetErrors) {
error('\nError! Found syntax errors!');
process.exit(1);

} else {
log('Success! Found no syntax errors.');
process.exit(0);
}

// ----------------------------------------------------------------------------

function printErrorForSnippet(snippet, variations) {
log('--------------------------------------------------------------------')
log(`Errors in file ./${path.relative(projectDir, snippet.file)}`)
log('In following snippet\n')
log(' ```java')
log(indentLines(snippet.original, 2))
log(' ```')
log('')

for (const variation of variations) {
log(`which was modified and compiled again as:
\`\`\`java
${indentLines(variation.content, 2)}
\`\`\`
which then ended up with errors:
${indentLines(variation.error.message, 2)}
`);
}
log('')
}

/**
* @param {string} content
*/
function compileSnippet(content) {
try {
parseJava(content);
return null;

} catch (e) {
// the Java parser uses this string in its error messages
if (!e.message.includes('sad panda'))
throw e;

if (e.message.length > 200) {
// cut off message text; the original length is too large
e.message = e.message.slice(0, 200);
}
return e;
}
}

function prepareSnippet(content) {
// Delete "import" statements, as they are mixed in with other code.
content = content.replace(/^import .*$/mug, '');
// `= ...;` is replaced by `= null`
content = content.replace(/= *[.][.][.];/mug, '= null;');
// `= ...` is replaced by `= null;` (additional semicolon)
content = content.replace(/= [.][.][.]/mug, '= null;');
// `, ...` is removed
content = content.replace(/, ?[.][.][.]/mug, '');
content = content.replace(/[.][.][.] ?,/mug, '');
// And other remaining `...` are removed
content = content.replace(/[.][.][.]|…/g, '');
// Sometimes `---` is used as a delimiter
content = content.replace(/^---+.*$/gm, '');
return content;
}

/**
* @param {string} content
*/
function snippetAsMethod(content) {
return `// Snippet Checker
class MyClass {
${ indentLines(content.trim(), 2) }
}
`;
}

/**
* @param {string} content
*/
function snippetAsCode(content) {
return `// Snippet Checker
class SnippetCheckerClass {
void snippetCheckerMethod() {
${ indentLines(content.trim(), 4) }
}
}
`;
}

/**
* @param {string[]} files
*/
function* extractSnippetsFromFiles(files) {
for (const filename of files) {
for (const section of extractSections(filename)) {
for (const snippet of extractSnippets(section.content)) {
yield {
file: filename,
...snippet,
};
}
}
}
}

/**
* @param {string} file
*/
function* extractSections(file) {
const content = fs.readFileSync(file, 'utf-8');
const sections = content.split(/^#/gm);

for (const content of sections) {
// Skip empty parts
if (content.trim() === "")
continue;

const heading = content.slice(0, content.indexOf('\n'));
yield {
heading,
content,
};
}
}

/**
* @param {string} section
*/
function* extractSnippets(section) {
// Note: [^] matches any character, including newlines
const re = /^(?:\s*<!--(.+)-->\n)?```([a-zA-Z]+)\s*\n([^]*?)\n```\s*$/gm;

let snippets;
while ((snippets = re.exec(section)) !== null) {
const language = snippets[2].toLowerCase();
const content = snippets[3];
let mode;

if ('java' !== language)
continue;

// Code snippets may have a configuration in form of an HTML comment.
// When a cds-mode comment exists, we ignore the language.
if (snippets[1]) {
const modeRegEx = /mode: ([^,;]+)/;
const result = modeRegEx.exec(snippets[1]);
if (result && result[1])
mode = result[1].trim();
}

yield { mode, content };
}
}

/**
* @param {string} dir
* @param {RegExp} fileRegEx
* @returns {string[]}
*/
function getFilesInDirectory(dir, fileRegEx) {
let results = [];
const files = fs.readdirSync(dir);

for (let file of files) {
file = path.resolve(dir, file);
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
results = results.concat(getFilesInDirectory(file));
} else {
if (!fileRegEx || file.match(fileRegEx))
results.push(file);
}
}
return results;
}

/**
* Indent the given string by `indent` whitespace characters.
*
* @param {string} str
* @param {number} indent
* @returns {string}
*/
function indentLines(str, indent) {
const indentStr = ' '.repeat(indent);
const lines = str.split(/\r\n?|\n/);
return lines.map(s => indentStr + s).join('\n');
}

17 changes: 17 additions & 0 deletions .github/java-snippet-checker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "java-snippet-checker",
"version": "0.0.1",
"description": "Markdown checker for Java snippets",
"type": "module",
"main": "check-java-snippets.js",
"author": "SAP SE (https://www.sap.com)",
"license": "SEE LICENSE IN LICENSE",
"repository": "cap-js/docs",
"homepage": "https://cap.cloud.sap/",
"scripts": {
"check": "node check-java-snippets.js"
},
"dependencies": {
"java-parser": "^2.3.0"
}
}
8 changes: 7 additions & 1 deletion .github/workflows/PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ jobs:
with:
node-version: 18.x
cache: 'npm'
- run: |
- name: Run CDS snippet checker
run: |
cd .github/cds-snippet-checker
npm install
npm run check
- name: Run Java snippet checker
run: |
cd .github/java-snippet-checker
npm install
npm run check
- run: npm ci
- run: npm test
- run: npm run docs:build
8 changes: 4 additions & 4 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ if (!siteURL.pathname.endsWith('/')) siteURL.pathname += '/'
const redirectLinks: Record<string, string> = {}

const latestVersions = {
java_services: '3.1.0',
java_cds4j: '3.1.0'
java_services: '3.2.0',
java_cds4j: '3.2.0'
}

const localSearchOptions = {
Expand Down Expand Up @@ -93,8 +93,8 @@ const config:UserConfig<CapireThemeConfig> = {
// IMPORTANT: Don't use getters here, as they are called again and again!
sidebar: menu,
nav: [
Object.assign(nav.find(i => i.text === 'Getting Started')!, {text:'Get Started'}),
Object.assign(nav.find(i => i.text === 'Cookbook')!, {text:'Guides'}),
{ ...nav.find(i => i.text === 'Getting Started') ?? {}, text: 'Get Started' },
{ ...nav.find(i => i.text === 'Cookbook') ?? {}, text: 'Guides' },
nav.find(i => i.text === 'CDS'),
nav.find(i => i.text === 'Node'),
nav.find(i => i.text === 'Java'),
Expand Down
2 changes: 2 additions & 0 deletions .vitepress/theme/Layout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ImplVariants from './components/implvariants/ImplVariants.vue'
import NavScreenMenuItem from './components/implvariants/NavScreenMenuItem.vue'
import NotFound from './components/NotFound.vue'
import Ribbon from './components/Ribbon.vue'
import ScrollToTop from './components/ScrollToTop.vue'
const isPreview = !!import.meta.env.VITE_CAPIRE_PREVIEW
Expand All @@ -18,6 +19,7 @@ const { frontmatter } = useData()

<Layout>
<template #layout-top>
<ScrollToTop />
<slot name="layout-top" />
</template>
<template #doc-top>
Expand Down
Loading

0 comments on commit 812ddfc

Please sign in to comment.