Skip to content
This repository was archived by the owner on Dec 8, 2025. It is now read-only.

Commit 0c03890

Browse files
committed
feat: repeated words check validation
1 parent 494b69d commit 0c03890

File tree

3 files changed

+104
-2
lines changed

3 files changed

+104
-2
lines changed

src/components/DrawerChecks.vue

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ q-list
103103
import { onBeforeUnmount, onMounted } from 'vue'
104104
import { useQuasar } from 'quasar'
105105
import { checkArticles } from 'src/tools/articles'
106+
import { checkRepeatedWords } from 'src/tools/repeated-words'
106107
import { checkHyphenation } from 'src/tools/hyphenation'
107108
import { checkInclusiveLanguage } from 'src/tools/inclusive-language'
108109
import { checkNonAscii } from 'src/tools/non-ascii'
@@ -153,6 +154,13 @@ const valChecks = [
153154
description: 'Check for common placeholders',
154155
icon: 'mdi-select-remove',
155156
click: () => placeholdersCheck()
157+
},
158+
{
159+
key: 'repeatedWords',
160+
title: 'Repeated Words Check',
161+
description: 'Check for accidental repeated terms',
162+
icon: 'mdi-repeat',
163+
click: () => repeatedWordsCheck()
156164
}
157165
]
158166
@@ -251,13 +259,33 @@ function placeholdersCheck (silent = false) {
251259
}
252260
}
253261
262+
function repeatedWordsCheck (silent) {
263+
const results = checkRepeatedWords(modelStore[docsStore.activeDocument.id].getValue())
264+
if (results.count < 1) {
265+
editorStore.setValidationCheckState('repeatedWords', 1)
266+
editorStore.setValidationCheckDetails('repeatedWords', [])
267+
if (!silent) {
268+
$q.notify({
269+
message: 'Looks good!',
270+
caption: 'No repeated terms found.',
271+
color: 'positive',
272+
icon: 'mdi-repeat'
273+
})
274+
}
275+
} else {
276+
editorStore.setValidationCheckState('repeatedWords', -2)
277+
editorStore.setValidationCheckDetails('repeatedWords', results)
278+
}
279+
}
280+
254281
function runAllChecks () {
255282
editorStore.clearErrors()
256283
articlesCheck(true)
257284
hyphenationCheck(true)
258285
inclusiveLangCheck(true)
259286
nonAsciiCheck(true)
260287
placeholdersCheck(true)
288+
repeatedWordsCheck(true)
261289
262290
if (editorStore.errors.length < 1) {
263291
$q.notify({
@@ -290,6 +318,9 @@ function runSelectedCheck (key) {
290318
case 'placeholders':
291319
placeholdersCheck(true)
292320
break
321+
case 'repeatedWords':
322+
repeatedWordsCheck(true)
323+
break
293324
}
294325
}
295326

src/stores/editor.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,16 @@ export const useEditorStore = defineStore('editor', {
7272
hyphenation: 0,
7373
inclusiveLanguage: 0,
7474
nonAscii: 0,
75-
placeholders: 0
75+
placeholders: 0,
76+
repeatedWords: 0
7677
},
7778
validationChecksDetails: {
7879
articles: [],
7980
hyphenation: [],
8081
inclusiveLanguage: [],
8182
nonAscii: [],
82-
placeholders: []
83+
placeholders: [],
84+
repeatedWords: []
8385
},
8486
wordWrap: true,
8587
workingDirectory: '',

src/tools/repeated-words.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { sortBy } from 'lodash-es'
2+
import { decorationsStore } from 'src/stores/models'
3+
4+
export function checkRepeatedWords (text) {
5+
const matchRgx = /\b(\w+)\s+\1\b/gi
6+
const textLines = text.split('\n')
7+
8+
const decorations = []
9+
const occurences = []
10+
const details = []
11+
const termCount = {}
12+
for (const [lineIdx, line] of textLines.entries()) {
13+
for (const match of line.matchAll(matchRgx)) {
14+
const term = match[1].toLowerCase()
15+
let occIdx = occurences.indexOf(term)
16+
if (occIdx < 0) {
17+
occIdx = occurences.push(term) - 1
18+
}
19+
decorations.push({
20+
options: {
21+
hoverMessage: {
22+
value: `Repeated term "${match[1]}" detected.`
23+
},
24+
className: 'dec-warning',
25+
minimap: {
26+
position: 1
27+
},
28+
glyphMarginClassName: 'dec-warning-margin'
29+
},
30+
range: {
31+
startLineNumber: lineIdx + 1,
32+
startColumn: match.index + 1,
33+
endLineNumber: lineIdx + 1,
34+
endColumn: match.index + 1 + match[0].length
35+
}
36+
})
37+
details.push({
38+
key: crypto.randomUUID(),
39+
group: occIdx + 1,
40+
message: match[1].toLowerCase(),
41+
range: {
42+
startLineNumber: lineIdx + 1,
43+
startColumn: match.index + 1,
44+
endLineNumber: lineIdx + 1,
45+
endColumn: match.index + 1 + match[0].length
46+
}
47+
})
48+
if (termCount[term]) {
49+
termCount[term]++
50+
} else {
51+
termCount[term] = 1
52+
}
53+
}
54+
}
55+
56+
decorationsStore.get('repeatedWords').set(decorations)
57+
58+
return {
59+
count: decorations.length,
60+
details: sortBy(details, d => d.range.startLineNumber),
61+
hasTextOutput: true,
62+
getTextOutput: () => {
63+
return `Repeated Words
64+
-------------
65+
${Object.entries(termCount).map(([k, v]) => `${k} (${v})`).join('\n')}
66+
`
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)