From 52e8624024ea6fcf5014633b60e7cb161ed0018e Mon Sep 17 00:00:00 2001 From: Steffen Waldmann Date: Mon, 18 May 2026 13:35:32 +0200 Subject: [PATCH] chore: remove cspell --- .github/etc/create-review.cjs | 301 ---------------------------------- .github/workflows/lint.yml | 76 --------- .markdownlint.yaml | 20 --- cspell.config.yaml | 89 ---------- package.json | 3 - project-words.txt | 23 --- 6 files changed, 512 deletions(-) delete mode 100644 .github/etc/create-review.cjs delete mode 100644 .github/workflows/lint.yml delete mode 100644 .markdownlint.yaml delete mode 100644 cspell.config.yaml delete mode 100644 project-words.txt diff --git a/.github/etc/create-review.cjs b/.github/etc/create-review.cjs deleted file mode 100644 index 8b0ec1ecd6..0000000000 --- a/.github/etc/create-review.cjs +++ /dev/null @@ -1,301 +0,0 @@ -const cspellRegExp = /^(.*\.md)(:\d+:?\d*)\s*- Unknown word \((.*?)\)\s+-- (.*?) Suggestions: (\[.*\])$/ -const markdownlintRegExp = /^(.*\.md)(:\d+:?\d*) ([^\s]+) (.*?)(\[.*?\])?( \[Context: .*\])?$/ - -const createSuggestionText = (suggestion) => '```suggestion\n' + suggestion + '\n```\n' -const createCspellSuggestionText = (suggestion, other) => createSuggestionText(suggestion) + `Or maybe one of these: ${other.map(el => `**${el}**`).join(', ')}?` -const createWordsWithoutSuggestionsText = (words) => `For the following words no suggestions could be found, consider adding them to the word list:\n${words.map(word => `* ${word}\n`).join('')}` -const createUnknownWordComment = (word) => `Fix the spelling mistake in "**${word}**" or add it to the **project-words.txt** list.` -const createMissingCodeFencesText = (lines) => -` -\`\`\`\`suggestion -${lines.join('\n')} -\`\`\`\` - -Please add a language tag. For plain text add \`txt\` as language tag. -` - -const getNoEmptyLinkText = () => 'No empty links. Please provide a link value.' -const getSpellingCorrectionTip = () => -` -Generally, for each spelling mistake there are 2 ways to fix it: -1. Fix the spelling mistake and commit it. -2. The word is incorrectly reported as misspelled → put the word on the **project-words.txt** list, located in the root project directory. -` - -const getInvalidUrlText = (text, link) => { - const updatedLink = link.replace('http', 'https') - return createSuggestionText(`${text}(${updatedLink})`) -} - -const escapeMarkdownlink = (link) => link.replace(/(\[|\(|\]|\))/g, "\\$1") -const createSuggestContainerTypeText = (suggestion) => createSuggestionText(suggestion) + 'You have to specify a container type. Possible values: **info**, **tip**, **warning**, **danger**, **details**, **code-group**, **raw**.' - -module.exports = async ({ github, require, exec, core }) => { - const { readFileSync, existsSync } = require('fs') - const { join, extname } = require('path') - const { BASE_DIR, PULL_NUMBER, HEAD_SHA, REPO, REPO_OWNER } = process.env - - const cspellLogFile = join(BASE_DIR, 'CSPELL.log') - const markdownlintLogFile = join(BASE_DIR, 'MARKDOWNLINT.log') - - const comments = [] - let body = '' - let lintErrorsText = '' - let spellingMistakesText = '' - - const result = await github.request('GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews', { - owner: REPO_OWNER, - repo: REPO, - pull_number: PULL_NUMBER - }) - - const linterErrors = [] - const spellingMistakes = [] - - result.data - .filter(review => review.body.includes('')) - .forEach(review => { - spellingMistakes.push(...(review.body.match(/\*(.*) /g) || [])) - linterErrors.push(...(review.body.match(/\*(.*) /g) || [])) - }) - - const { data } = await github.request('GET /repos/{owner}/{repo}/pulls/{pull_number}/files', { - owner: REPO_OWNER, - repo: REPO, - pull_number: PULL_NUMBER, - headers: { - accept: 'application/vnd.github.diff' - } - }) - - const diffs = {} - data.filter(obj => extname(obj.filename) === '.md') - .forEach(obj => { - diffs[obj.filename.replace('./', '')] = obj.patch?.split('\n') ?? [] - }) - - if (existsSync(markdownlintLogFile)) { - const matches = readFileSync(markdownlintLogFile, 'utf-8') - .split('\n') - .filter(Boolean) - .map(line => line.replace(`${BASE_DIR}/`, '').match(markdownlintRegExp)) - - /* - test.md:15:1 MD011/no-reversed-links Reversed link syntax [(test)[link.de]] -> - - test.md:15:1 MD011/no-reversed-links Reversed link syntax [(test)[link.de]] - test.md - :15:1 - MD011/no-reversed-links - Reversed link syntax - [(test)[link.de]] - - */ - for (let [, path, pointer, rule, description, details, context] of matches) { - let contextText = '' - let comment - - if (!fileIsInDiff(path)) continue - - if (rule === 'MD011/no-reversed-links') { - const detailValue = details.slice(1, -1) - contextText = `[Context: "${detailValue}"]` - - const { line, position } = await findPositionInDiff(detailValue, path) - if (!line || position < 0) continue - - const [, link, text] = detailValue.match(/\((.*?)\)\[(.*?)\]/) - const suggestion = line.replace(detailValue, `[${text}](${link})`).replace('+', '') - const commentBody = createSuggestionText(suggestion) - comment = { path, position, body: commentBody } - } - - if (rule === 'MD042/no-empty-links') { - let link = context.match(/\[Context: "(\[.*?)"\]/)[1] - // if the context is too long, markdownlint-cli will truncate the string and append "..." at the end - if (link.endsWith('...')) { - link = link.substring(0, link.length - 3) - } - contextText = `[Context: "${escapeMarkdownlink(link)}"]` - const { position } = await findPositionInDiff(link, path) - if (position < 0) continue - comment = { path, position, body: getNoEmptyLinkText() } - } - - if (rule === 'MD040/fenced-code-language') { - contextText = '' - const codeBlockLines = findCodeBlock(path, +pointer.slice(1)) - const { start, end } = await findCodeBlockInDiff(codeBlockLines, path) - if (start < 0 || end < 0) continue - codeBlockLines[0] = codeBlockLines[0] + 'txt' - comment = { path, body: createMissingCodeFencesText(codeBlockLines), position: start } - } - - if (rule === 'search-replace') { - // [prefer-https-links: https links should be prefered] -> prefer-https-links - const ruleName = details.split(':')[0].slice(1) - if (ruleName === 'prefer-https-links') { - const [, text, link] = context.match(/\[Context:.*(\[.*\])(\(.*\)).*\]/) - description = 'https links should be preferred' - contextText = `[Context: "${escapeMarkdownlink(text + link)}"]` - const { line, position } = await findPositionInDiff(text + link, path) - if (!line || position < 0) continue - comment = { path, position, body: getInvalidUrlText(text, link.slice(1, -1)) } - } - - if (ruleName === 'custom-containers-requires-type') { - const [, row] = pointer.split(':') - const affectedLine = getLineFromFile(path, +row) - const containerType = suggestContainerType(affectedLine) || 'info' - const { line, position } = await findPositionInDiff(affectedLine, path) - - if (!line || position < 0) continue - const correctedLine = `::: ${containerType} ${affectedLine.split(':::').slice(1).join('').trim()}` - - description = 'container type should be specified' - contextText = `[Context: "${affectedLine}"]` - comment = { path, position, body: createSuggestContainerTypeText(correctedLine) } - } - } - - const text = `* **${path}**${pointer} ${description} ${contextText} ` - if (!linterErrors.find(el => el === text)) { - lintErrorsText += text + '\n' - comments.push(comment) - } - } - } - - if (existsSync(cspellLogFile)) { - let lines = readFileSync(cspellLogFile, 'utf-8').split('\n') - lines = Array.from({ length: lines.length / 2 }, (_el, idx) => lines[idx * 2] + lines[idx * 2 + 1].replace(/\t/g, '')) - - // we will create a review comment for each match - const matches = lines.map(line => line.replace(`${BASE_DIR}/`, '').match(cspellRegExp)) - const wordsWithoutSuggestions = [] - for (const [, path, pointer, word, context, suggestionString] of matches) { - if (!fileIsInDiff(path)) continue - const text = `* **${path}**${pointer} Unknown word "**${word}**" ` - if (spellingMistakes.find(el => el === text)) continue - // from "[s1, s2, s3]" to [ "s1", "s2", "s3" ] - const suggestions = suggestionString - .slice(1, -1) // remove brackets - .replace(/ /g, '') - .split(',') - .filter(Boolean) // remove empty strings - - const { line, position } = await findPositionInDiff(context, path) - if (!line || position < 0) continue - - // Github requires that no path starts with './', but cspell provides the paths exactly in this format - const properlyStructuredPath = path.replace(/^\.\//, '') - if (suggestions.length > 0) { - // replace word with first suggestions and remove first "+" sign - const suggestion = line.replace(word, suggestions[0]).replace('+', '') - const commentBody = createCspellSuggestionText(suggestion, suggestions.slice(1)) - comments.push({ path: properlyStructuredPath, position, body: commentBody }) - } else { - comments.push({ path: properlyStructuredPath, position, body: createUnknownWordComment(word) }) - wordsWithoutSuggestions.push(word) - } - spellingMistakesText += text + '\n' - } - - if (wordsWithoutSuggestions.length > 0 && comments.length > 0) { - spellingMistakesText += `\n${createWordsWithoutSuggestionsText(wordsWithoutSuggestions)}\n` - } - - if (matches.length > 0 && comments.length > 0) { - spellingMistakesText += `${getSpellingCorrectionTip()}\n` - } - } - - if (lintErrorsText) { - body += `Linting Errors\n---\n${lintErrorsText}` - } - - if (spellingMistakesText) { - body += `\nSpelling Mistakes\n---\n${spellingMistakesText}` - } - - if (body) { - body = '\n' + body - - await github.rest.pulls.createReview({ - owner: REPO_OWNER, - repo: REPO, - pull_number: PULL_NUMBER, - commit_id: HEAD_SHA, - body, - event: 'COMMENT', - comments - }) - } - - function fileIsInDiff(file) { - return typeof getDiff(file) !== 'undefined' - } - - function getDiff(file) { - const k = file.replace('./', '') - if (!(k in diffs)) throw new Error(`There is no diff for file ${file}. Diffs found for ${Object.keys(diffs).join('\n')}`) - return diffs[k] - } - - async function findPositionInDiff(context, file) { - const diff = getDiff(file) - - if (!diff) return { position: -1 } - - const idxToStartingCountingFrom = diff.findIndex(line => line.startsWith('@@') && !line.includes(' errors is in file with diff, but errors was not introduced with current PR - if (idxToStartingCountingFrom === -1 || idxOfLineToSearch === -1) { - return { position: -1 } - } - const position = idxOfLineToSearch - idxToStartingCountingFrom - return { line: diff[idxOfLineToSearch], position } - } - - async function findCodeBlockInDiff(lines, file) { - const diff = getDiff(file) - - if (!diff) return { position: -1 } - - let start = -1 - let end = -1 - for (let i = 0; i < diff.length; i++) { - for (let j = 0; j < lines.length; j++) { - if (diff[i + j].replace(/[-+]/, '') !== lines[j]) { - break - } - if (j === lines.length - 1) { - start = i - end = i + j - } - } - } - - if (start === -1 || end === -1) { - return { start: -1, end: -1 } - } - - const idxToStartingCoutingFrom = diff.findIndex(line => line.startsWith('@@')) - return { start: start - idxToStartingCoutingFrom, end: end - idxToStartingCoutingFrom } - } - - // startIdx starts at 1 - function findCodeBlock(file, startIdx) { - const lines = readFileSync(join(BASE_DIR, file), 'utf-8').split(/\n\r?/) - const endIdx = lines.findIndex((el, idx) => idx >= startIdx && /`{3,}/.test(el.trim())) - return lines.slice(startIdx - 1, endIdx + 1) - } - - function suggestContainerType(line) { - return (line.toLowerCase().match(/(info|tip|warning|danger|details|code-group|raw)/) || [])[0] - } - - function getLineFromFile(file, lineNumber) { - return readFileSync(join(BASE_DIR, file), 'utf-8').split(/\n\r?/)[lineNumber - 1] - } -} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 1770a1baa9..0000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Lint - -on: - pull_request: - branches: [main] - paths: - - '**.md' - merge_group: - workflow_dispatch: - -jobs: - suggestions: - runs-on: ubuntu-latest - if: '! github.event.pull_request.head.repo.fork' - permissions: - pull-requests: write - steps: - - name: Checkout SAP repo - run: | - git config --global credential.helper "cache --timeout=3600" - echo -e "url=https://user:${GH_TOKEN}@github.com\n" | git credential approve - echo -e "url=https://user:${GH_TOKEN_TOOLS_DOCS}@github.tools.sap\n" | git credential approve - git clone --depth 1 --no-single-branch https://github.tools.sap/cap/docs docs - cd docs - git checkout $GITHUB_HEAD_REF || git checkout main - git submodule update --init --recursive - cd @external - git checkout $GITHUB_HEAD_REF - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_TOKEN_TOOLS_DOCS: ${{ secrets.GH_TOKEN_TOOLS_DOCS }} - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - cache-dependency-path: docs/package-lock.json - - run: npm ci - working-directory: docs - - run: git checkout ${{ github.head_ref }} - working-directory: docs/@external - - name: Get changes - id: changes - working-directory: docs/@external - run: | - echo "DIFF_FILES=$(git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- '*.md' | xargs)" >> "$GITHUB_OUTPUT" - - name: Run cspell - if: steps.changes.outputs.DIFF_FILES != '' - id: cspell - continue-on-error: true - working-directory: docs/@external - run: | - npx cspell --no-progress --show-suggestions --show-context ${{ steps.changes.outputs.DIFF_FILES }} >> ${{ github.workspace }}/docs/@external/CSPELL.log - - name: Run markdownlint - if: steps.changes.outputs.DIFF_FILES != '' - id: markdownlint - continue-on-error: true - working-directory: docs/@external - run: | - npx markdownlint-cli --output ${{ github.workspace }}/docs/@external/MARKDOWNLINT.log -r markdownlint-rule-search-replace ${{ steps.changes.outputs.DIFF_FILES }} - - name: Create review - id: create_review - if: steps.cspell.outcome == 'failure' || steps.markdownlint.outcome == 'failure' - uses: actions/github-script@v6 - env: - SHA: ${{ github.event.pull_request.head.sha }} - BASE_DIR: ${{ github.workspace }}/docs/@external - BASE_SHA: ${{ github.event.pull_request.base.sha }} - PULL_NUMBER: ${{ github.event.number }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - REPO: 'docs' - REPO_OWNER: 'cap-js' - with: - script: | - const script = require('${{ github.workspace }}/docs/@external/.github/etc/create-review.cjs') - await script({github, context, core, require, exec}) diff --git a/.markdownlint.yaml b/.markdownlint.yaml deleted file mode 100644 index 48d690ad45..0000000000 --- a/.markdownlint.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# For all rules see https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md -default: false -fenced-code-language: true -no-reversed-links: true -code-fence-style: - style: backtick -# proper-names: -# names: -# - SQLite -# - VS Code -#no-bare-urls: true -no-empty-links: true -search-replace: - rules: - - name: prefer-https-links - message: https links should be prefered - searchPattern: /\[.*?\]\(http:\/\/(?!localhost).*?\)/g - - name: custom-containers-requires-type - searchPattern: "/(?/gm - - - name: markdown_link - pattern: /-?\s*([*#]|\[.*?\])\(.*?\)/gm - - - name: heading_ids - pattern: /\{\s*#+.+\}/ - - - name: code-group - pattern: /:{3}\scode-group[\s\S]*?:{3}/gm - - - name: href_link - pattern: /href="[\s\S]*"/gm - - - name: fragments - pattern: /<(Fragment|span|div)\s+(id|target)="#?.*"[\s.]*/?>/g - - - name: heading - pattern: /^(#{1,6}.*)/ - - - name: bold_text - pattern: /\*{2}.*\*{2}|__.*__/gm - - - name: italic_text - pattern: /\*.*\*|_.*_/gm - - - name: i_tags - pattern: /.*<\/i>/g - - - name: cli_option - pattern: /(-[^\s]+)/g - - - name: property - pattern: /`?`\.[a-zA-Z]*?/g - - - name: property_in_code_block - pattern: /`(\..*)+?`/gm diff --git a/package.json b/package.json index 2839c9d5fe..f797be2837 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,9 @@ "@types/express": "latest", "@typescript-eslint/parser": "^8.0.0", "adm-zip": "^0.5.10", - "cspell": "^10", "express": "^5", "fflate": "^0.8.2", "globals": "^17.4.0", - "markdownlint-cli": ">=0.35.0", - "markdownlint-rule-search-replace": "^1.1.1", "monaco-editor": "^0.55.1", "sass": "^1.62.1", "vite": "^7", diff --git a/project-words.txt b/project-words.txt deleted file mode 100644 index 7ad8dd4989..0000000000 --- a/project-words.txt +++ /dev/null @@ -1,23 +0,0 @@ -aggregatable -Appl -Catweazle -CMIS -creat -CRUDQ -HCQL -IDONTEXIST -isdir -modulith -moduliths -nolink -overlayed -Reenskaug -Schlachter -SusaaS -SWAPI -Trygve -Undeploying -undiscloses -unlocalized -Woltmann -asprof