Skip to content

Commit

Permalink
Merge pull request #1 from cap-js/main
Browse files Browse the repository at this point in the history
Update to latest SAP version
  • Loading branch information
MartinStenzig authored Aug 31, 2023
2 parents 097174f + 7de7a3e commit fe593af
Show file tree
Hide file tree
Showing 113 changed files with 6,166 additions and 2,738 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# This files defines code ownership.

* @renejeglinsky @manjuX
* @renejeglinsky @smahati
283 changes: 283 additions & 0 deletions .github/etc/create-review.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
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")

module.exports = async ({ github, require, exec, core }) => {
const { readFileSync, existsSync } = require('fs')
const { join } = require('path')
const { SHA, BASE_DIR, BASE_SHA, 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 = ''

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 [error, path, pointer, rule, description, details, context] of matches) {
let contextText = ''

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)

comments.push({ path, position, body: commentBody })
}

if (rule === 'MD042/no-empty-links') {
const link = context.match(/\[Context: "(\[.*?\]\(\))"/)[1]

contextText = `[Context: "${escapeMarkdownlink(link)}"]`

const { position } = await findPositionInDiff(link, path)

if (position < 0) {
continue
}

comments.push({ 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'

comments.push({ path, body: createMissingCodeFencesText(codeBlockLines), start_line: start, line: end })
}

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
}

comments.push({ path, position, body: getInvalidUrlText(text, link.slice(1, -1)) })
}
}

lintErrorsText += `* **${path}**${pointer} ${description} ${contextText}\n`
}
}

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 [error, path, pointer , word, context, suggestionString] of matches) {

// 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
}

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, position, body: commentBody })
} else {
comments.push({ path, position, body: createUnknownWordComment(word) })

wordsWithoutSuggestions.push(word)
}

spellingMistakesText += `* **${path}**${pointer} Unknown word "**${word}**"\n`
}

if (wordsWithoutSuggestions.length > 0) {
spellingMistakesText += `\n${createWordsWithoutSuggestionsText(wordsWithoutSuggestions)}\n`
}

if (matches.length > 0) {
spellingMistakesText += `${getSpellingCorrectionTip()}\n`
}

}

if (lintErrorsText) {
body += `Linting Errors\n---\n${lintErrorsText}`
}

if (spellingMistakesText) {
body += `\nSpelling Mistakes\n---\n${spellingMistakesText}`
}

if (body) {
await github.rest.pulls.createReview({
owner: REPO_OWNER,
repo: REPO,
pull_number: PULL_NUMBER,
commit_id: HEAD_SHA,
body,
event: 'COMMENT',
comments
})
}

async function getDiff(file) {
let diff = ''
const opts = {
listeners: {

stdout: (data) => {
diff += data.toString();
}
},
cwd: BASE_DIR
}

await exec.exec(`git diff ${BASE_SHA} ${SHA} -- ${file}`, [], opts)

return diff.split('\n')
}

async function findPositionInDiff(context, file) {
const diff = await getDiff(file)

const idxToStartingCoutingFrom = diff.findIndex(line => line.startsWith('@@'))
const idxOfLineToSearch = diff.findIndex(line => line.trim().startsWith('+') && line.replace(/ /g, '').includes(context.replace(/ /g, '')))

// context does not exist in diff --> errors is in file with diff, but errors was not introduced with current PR
if (idxToStartingCoutingFrom === -1 || idxOfLineToSearch === -1) {
return { position: -1 }
}

const position = idxOfLineToSearch - idxToStartingCoutingFrom

return { line: diff[idxOfLineToSearch], position }
}

async function findCodeBlockInDiff(lines, file) {
const diff = await getDiff(file)

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)
}
}
73 changes: 73 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Lint

on:
pull_request:
branches: [main]
paths:
- '**.md'
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_PARENT}@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_PARENT: ${{ secrets.GH_TOKEN_PARENT }}
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 18.x
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
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
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})
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
- run: npm run docs:build
env:
SITE_HOSTNAME: https://cap-js.github.io/docs
VITE_CAPIRE_PREVIEW: true
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ node_modules/
cache/
dist/
.DS_Store
.idea
*.iml
17 changes: 17 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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
Loading

0 comments on commit fe593af

Please sign in to comment.