-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add spell-checker-filename (#551)
Добавляем экшон для проверки названий файлов. Если есть проблемы выдает предупреждение в PR-е
- Loading branch information
1 parent
8e5daf6
commit 76960e2
Showing
15 changed files
with
408 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module.exports = { | ||
root: false, | ||
parserOptions: { | ||
project: './tsconfig.json', | ||
tsconfigRootDir: __dirname, | ||
}, | ||
rules: { | ||
'@typescript-eslint/naming-convention': 'off', // [Reason] 'snake_case' is expected naming | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
name: 'Spell Checker filename' | ||
description: 'Spell Checker filename' | ||
inputs: | ||
token: | ||
required: false | ||
description: 'token with access to your repository' | ||
runs: | ||
using: 'node20' | ||
main: 'dist/index.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import config from '../../jest.config'; | ||
|
||
export default config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "@actions-internal/spell-checker-filename", | ||
"version": "0.0.0", | ||
"main": "src/main.ts", | ||
"license": "MIT", | ||
"private": true, | ||
"devDependencies": { | ||
"@types/node": "^22.10.7", | ||
"@types/nspell": "^2.1.6", | ||
"typescript": "^5.7.3" | ||
}, | ||
"dependencies": { | ||
"@actions/core": "^1.11.1", | ||
"@actions/github": "^6.0.0", | ||
"async-mutex": "^0.5.0", | ||
"nspell": "^2.1.5" | ||
}, | ||
"scripts": { | ||
"prebuild": "shx rm -rf dist/*", | ||
"build": "esbuild ./src/main.ts --bundle --outfile=dist/index.js --platform=node --packages=bundle", | ||
"test": "jest --passWithNoTests" | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
shared/spell-checker-filename/src/entities/repositories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
export interface SpellCheckerRepository { | ||
/** | ||
* Проверяет слово на ошибки | ||
*/ | ||
correct(word: string): Promise<boolean>; | ||
|
||
/** | ||
* Предлагает варианты исправления слова | ||
*/ | ||
suggest(word: string): Promise<string[]>; | ||
|
||
/** | ||
* Добавляет слова в словарь | ||
*/ | ||
addToDict(words: string[]): Promise<void>; | ||
} | ||
|
||
export interface GithubRepository { | ||
/** | ||
* Возвращает список измененных файлов из pull request'а | ||
*/ | ||
pullRequestPaths(): Promise<string[]>; | ||
|
||
/** | ||
* Создает предупреждение для файла в pull request'е | ||
*/ | ||
warningFile(path: string, message: string): Promise<void>; | ||
} | ||
|
||
export interface Repositories { | ||
spellCheckerRepository: SpellCheckerRepository; | ||
githubRepository: GithubRepository; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import * as core from '@actions/core'; | ||
import { NSpellSpellChecker } from './repositories/spell'; | ||
import { GitHub } from './repositories/github'; | ||
import { ActionService } from './service/action'; | ||
import { Repositories } from './entities/repositories'; | ||
|
||
function prodRepositories(): Repositories { | ||
return { | ||
spellCheckerRepository: new NSpellSpellChecker(), | ||
githubRepository: new GitHub(), | ||
}; | ||
} | ||
|
||
async function main(): Promise<void> { | ||
try { | ||
const repositories: Repositories = prodRepositories(); | ||
|
||
const action = new ActionService(repositories); | ||
|
||
await action.run(); | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
core.setFailed(error.message); | ||
} | ||
} | ||
} | ||
|
||
void main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import * as core from '@actions/core'; | ||
import * as github from '@actions/github'; | ||
|
||
import { GithubRepository } from '../entities/repositories'; | ||
|
||
export class GitHub implements GithubRepository { | ||
private readonly octokit: ReturnType<typeof github.getOctokit>; | ||
|
||
public constructor() { | ||
const token = core.getInput('token', { required: true }); | ||
this.octokit = github.getOctokit(token); | ||
} | ||
|
||
public async pullRequestPaths(): Promise<string[]> { | ||
if (github.context.payload.pull_request === undefined) { | ||
throw new Error('Not found information about Pull Request'); | ||
} | ||
|
||
const response = await this.octokit.graphql<{ | ||
repository: { | ||
pullRequest: { | ||
files: { | ||
nodes: Array<{ | ||
path: string; | ||
}>; | ||
}; | ||
}; | ||
}; | ||
}>( | ||
` | ||
query($owner:String!, $repo: String!, $pull_number: Int!, $first: Int!) { | ||
repository(owner: $owner, name: $repo) { | ||
pullRequest(number: $pull_number) { | ||
files(first:$first) { | ||
nodes { | ||
path | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`, | ||
{ | ||
owner: github.context.repo.owner, | ||
repo: github.context.repo.repo, | ||
pull_number: github.context.payload.pull_request.number, | ||
first: 100, | ||
}, | ||
); | ||
|
||
return response.repository.pullRequest.files.nodes.map((file) => file.path); | ||
} | ||
|
||
public async warningFile(path: string, message: string): Promise<void> { | ||
core.warning(message, { | ||
title: 'Проверка опечаток', | ||
file: path, | ||
}); | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
shared/spell-checker-filename/src/repositories/spell.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { expect, test } from '@jest/globals'; | ||
|
||
import { NSpellSpellChecker } from './spell'; | ||
|
||
const spellChecker = new NSpellSpellChecker(); | ||
|
||
test('NSpellSpellChecker check', async () => { | ||
expect(await spellChecker.correct('hello')).toBeTruthy(); | ||
expect(await spellChecker.correct('helloo')).toBeFalsy(); | ||
}); | ||
|
||
test('NSpellSpellChecker check personal dictionary', async () => { | ||
expect(await spellChecker.correct('svg')).toBeTruthy(); | ||
expect(await spellChecker.correct('src')).toBeTruthy(); | ||
}); | ||
|
||
test('NSpellSpellChecker suggest', async () => { | ||
expect(await spellChecker.suggest('helloo')).toEqual(['hello', 'halloo', 'hellos']); | ||
}); | ||
|
||
test('NSpellSpellChecker check', async () => { | ||
expect(await spellChecker.correct('npm')).toBeFalsy(); | ||
await spellChecker.addToDict(['npm']); | ||
expect(await spellChecker.correct('npm')).toBeTruthy(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
import nspell from 'nspell'; | ||
import { Mutex } from 'async-mutex'; | ||
import { SpellCheckerRepository } from '../entities/repositories'; | ||
|
||
const PERSONAL_DICTIONARY = ['svg', 'src'].join('\n'); | ||
|
||
export class NSpellSpellChecker implements SpellCheckerRepository { | ||
private spell: ReturnType<typeof nspell> | null = null; | ||
private readonly mutexLoad = new Mutex(); | ||
|
||
private async loadNSpell() { | ||
// MIT словарь для проверки на английском языке | ||
const urlEnDict = | ||
'https://raw.githubusercontent.com/wooorm/dictionaries/8cfea406b505e4d7df52d5a19bce525df98c54ab/dictionaries/en/'; | ||
|
||
// TODO: Кэширование на устройстве? | ||
const aff = await fetch(urlEnDict + 'index.aff'); | ||
const dic = await fetch(urlEnDict + 'index.dic'); | ||
|
||
this.spell = nspell(Buffer.from(await aff.arrayBuffer()), Buffer.from(await dic.arrayBuffer())); | ||
this.spell.personal(PERSONAL_DICTIONARY); | ||
} | ||
|
||
private async load() { | ||
/** | ||
* Блокируем мьютексом, чтобы словарь не загружался несколько раз одновременно | ||
*/ | ||
const release = await this.mutexLoad.acquire(); | ||
|
||
if (!this.spell) { | ||
await this.loadNSpell(); | ||
} | ||
|
||
release(); | ||
} | ||
|
||
public async correct(word: string): Promise<boolean> { | ||
await this.load(); | ||
|
||
return this.spell!.correct(word); | ||
} | ||
|
||
public async suggest(word: string): Promise<string[]> { | ||
await this.load(); | ||
|
||
return this.spell!.suggest(word); | ||
} | ||
|
||
public async addToDict(words: string[]): Promise<void> { | ||
await this.load(); | ||
|
||
this.spell!.personal(words.join('\n')); | ||
} | ||
} | ||
|
||
export class MockSpellChecker implements SpellCheckerRepository { | ||
public dict = new Set<string>(); | ||
public suggestMap = new Map<string, string[]>(); | ||
|
||
public async correct(word: string): Promise<boolean> { | ||
return this.dict.has(word); | ||
} | ||
|
||
public async suggest(word: string): Promise<string[]> { | ||
return this.suggestMap.get(word) ?? []; | ||
} | ||
|
||
public async addToDict(words: string[]): Promise<void> { | ||
words.forEach((word) => { | ||
this.dict.add(word); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { expect, test } from '@jest/globals'; | ||
import { ActionService } from './action'; | ||
import { MockSpellChecker } from '../repositories/spell'; | ||
|
||
test('Action checkPath', async () => { | ||
const repositories = { | ||
spellCheckerRepository: new MockSpellChecker(), | ||
githubRepository: {} as any, | ||
}; | ||
|
||
repositories.spellCheckerRepository.dict = new Set(['path', 'to', 'file', 'svg']); | ||
|
||
const action = new ActionService(repositories); | ||
|
||
expect(await action.checkPath('path/to_file')).toEqual([]); | ||
expect(await action.checkPath('path/to_file/40.svg')).toEqual([]); | ||
expect(await action.checkPath('path/to_file/bad')).toEqual(['bad']); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { Service } from './service'; | ||
|
||
export class ActionService extends Service { | ||
/** | ||
* Проверяет путь на наличие ошибок в словах | ||
*/ | ||
public async checkPath(path: string): Promise<string[]> { | ||
const result: string[] = []; | ||
const words = path | ||
.replace(/[-_\/\d\.]/g, ' ') | ||
.split(' ') | ||
.filter((word) => word); | ||
|
||
for (const word of words) { | ||
if (await this.repositories.spellCheckerRepository.correct(word)) { | ||
continue; | ||
} | ||
|
||
result.push(word); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Запускает проверку всех путей из пулл реквеста | ||
*/ | ||
public async run(): Promise<void> { | ||
const paths = await this.repositories.githubRepository.pullRequestPaths(); | ||
|
||
for await (const filePath of paths) { | ||
const word = await this.checkPath(filePath); | ||
if (!word.length) continue; | ||
|
||
await this.repositories.githubRepository.warningFile( | ||
filePath, | ||
`Возможно ошибочное написание слова '${word.join(' ')}' в пути к файлу ${filePath}`, | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { Repositories } from '../entities/repositories'; | ||
|
||
/** | ||
* Базовый класс сервиса, для доступа к [адаптерам](../repositories/) | ||
*/ | ||
export class Service { | ||
protected readonly repositories: Repositories; | ||
|
||
public constructor(repositories: Repositories) { | ||
this.repositories = repositories; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es2015", | ||
"moduleResolution": "node", | ||
"module": "commonjs", | ||
"noEmit": true, | ||
"rootDir": "./src", | ||
"strict": true, | ||
"noImplicitAny": true, | ||
"esModuleInterop": true, | ||
"resolveJsonModule": true | ||
}, | ||
"include": ["src/**/*.ts", ".eslintrc.js"], | ||
"exclude": ["node_modules"] | ||
} |
Oops, something went wrong.