diff --git a/bun.lockb b/bun.lockb index 6092015..5dbe839 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index f4b2d43..76df5ad 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@types/node": "^20.12.2", "bun-types": "latest", "esbuild": "^0.20.2", + "textlint-rule-preset-ja-technical-writing": "^12.0.2", "textlint-scripts": "^14.0.4", "textlint-tester": "^14.0.4", "typescript": "^5.4.3", diff --git a/test/integration/fixtures/smoke/textlint-rule-preset-ja-technical-writing/main.typ b/test/integration/fixtures/smoke/textlint-rule-preset-ja-technical-writing/main.typ new file mode 100644 index 0000000..069ac9d --- /dev/null +++ b/test/integration/fixtures/smoke/textlint-rule-preset-ja-technical-writing/main.typ @@ -0,0 +1,101 @@ += textlint for Typst + +== sentence-length + +あいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえおあいうえお。 + +== max-comma + +組版処理システムの例としては、Tex (LaTeX), SATySFi, Typst, Twight, SILEなどがあります。 + +== max-ten + +これは、これは、これは、これは、これはだめ。 + +== max-kanji-continuous-len + +情報処理学会は、電気学会、照明学会、応用物理学会、映像情報メディア学会および電子情報通信学会とともに電気系6学会の1つに数えられる存在です。 + +== arabic-kanji-numbers + +Fibonacci数列の十番目の項は55です。 + +1時的なファイルは`/tmp`に保存されます。 + +== no-mix-dearu-desumasu + +今日はいい天気ですね。今日はいい天気である。 + +== no-mixed-period + +これは問題ないです。 + +「これはセリフ」 + +english only + +これは句点がありません + +== no-double-negative-ja + +それが事件の発端だったといえなくもない。 + +確かにそういった懸念はない事はない。 + +== no-dropping-the-ra + +お刺身を食べれない。 + +== no-doubled-conjunctive-particle-ga + +今日は早朝から出発したが、定刻には間に合わなかったが、無事会場に到着した。 + +== no-doubled-conjunction + +かな漢字変換により漢字が多用される傾向がある。しかし漢字の多用が読みにくさをもたらす側面は否定できない。しかし、平仮名が多い文は間延びした印象を与える恐れもある。 + +== no-doubled-joshi + +私は彼は好きだ。 + +== no-nfd + +ホ゜ケット エンシ゛ン。 + +== no-exclamation-question-mark + +技術文書では、感嘆符、疑問符は基本的には使用しないでください! + +== no-hankaku-kana + +半角カタカナを使用しないでください。 + +== no-weak-phrase + +この表現には問題があるかもしれないです。 + +== no-successive-word + +これは問題ない文章です。 + +これはは問題ある文章です。 + +これは問題あるある文章です。 + +== no-abusage + +ウインドウ幅が可変すると、レイアウトが崩れる。 + +今朝起きた事件に法律を適応する。 + +== no-redundant-expression + +これは省略することが可能です。 + +== ja-unnatural-alphabet + +リイr−ス。 + +== no-unmatched-pair + +これは(秘密)です。 diff --git a/test/integration/linting.test.ts b/test/integration/linting.test.ts new file mode 100644 index 0000000..fb4440d --- /dev/null +++ b/test/integration/linting.test.ts @@ -0,0 +1,174 @@ +import fs from "node:fs"; +import path from "node:path"; +import { + TextlintKernel, + type TextlintKernelRule, + type TextlintResult, +} from "@textlint/kernel"; +// @ts-expect-error +import { rules, rulesConfig } from "textlint-rule-preset-ja-technical-writing"; + +import { beforeAll, describe, expect, it } from "vitest"; + +import typstPlugin from "../../src"; + +describe("linting", () => { + describe("smoke tests", () => { + describe("textlint-rule-preset-ja-technical-writing", () => { + let textlintResult: TextlintResult; + + beforeAll(async () => { + const kernel = new TextlintKernel(); + textlintResult = await kernel.lintText( + fs.readFileSync( + path.join( + __dirname, + "./fixtures/smoke/textlint-rule-preset-ja-technical-writing/main.typ", + ), + "utf-8", + ), + { + ext: ".typ", + plugins: [ + { + pluginId: "typst", + plugin: typstPlugin, + }, + ], + rules: [ + // Set each rule in the preset individually + ...Object.entries(rules).map( + ([id, rule]) => + ({ + ruleId: `ja-technical-writing/${id}`, + rule: rule, + options: rulesConfig[id] || true, + }) as TextlintKernelRule, + ), + ], + }, + ); + }); + + // Rule test configurations + const ruleTests = [ + { + ruleId: "sentence-length", + expectedMessage: "exceeds the maximum sentence length", + }, + { + ruleId: "max-ten", + expectedMessage: '一つの文で"、"を4つ以上使用', + }, + { + ruleId: "max-kanji-continuous-len", + expectedMessage: "漢字が7つ以上連続", + }, + { + ruleId: "no-mix-dearu-desumasu", + expectedMessage: '"ですます"調 でなければなりません', + }, + { + ruleId: "ja-no-mixed-period", + expectedMessage: '文末が"。"で終わっていません', + }, + { + ruleId: "no-doubled-joshi", + expectedMessage: "一文に二回以上利用されている助詞", + }, + { + ruleId: "no-dropping-the-ra", + expectedMessage: "ら抜き言葉を使用", + }, + { + ruleId: "no-doubled-conjunctive-particle-ga", + expectedMessage: '逆接の接続助詞 "が" が二回以上使われています', + }, + { + ruleId: "no-doubled-conjunction", + expectedMessage: "同じ接続詞(しかし)が連続", + }, + { + ruleId: "no-exclamation-question-mark", + expectedMessage: 'Disallow to use "!"', + }, + { + ruleId: "no-hankaku-kana", + expectedMessage: "Disallow to use 半角カタカナ", + }, + { + ruleId: "ja-no-weak-phrase", + expectedMessage: '弱い表現: "かも" が使われています', + }, + { + ruleId: "ja-no-successive-word", + expectedMessage: "が連続して2回使われています", + }, + { + ruleId: "ja-no-redundant-expression", + expectedMessage: 'することが可能です"は冗長な表現', + }, + { + ruleId: "ja-unnatural-alphabet", + expectedMessage: "不自然なアルファベット", + }, + { + ruleId: "no-unmatched-pair", + expectedMessage: "Cannot find a pairing character for (", + }, + ]; + + // Special cases with multiple expected messages + const multiMessageTests = [ + { + ruleId: "arabic-kanji-numbers", + expectedMessages: ["十番目 => 10番目", "1時的 => 一時的"], + }, + { + ruleId: "no-double-negative-ja", + expectedMessages: [ + "二重否定: 〜なくもない", + "二重否定: 〜ないことはない", + ], + }, + { + ruleId: "no-nfd", + expectedMessages: ['ホ゜" => "ポ"', 'シ゛" => "ジ"'], + }, + { + ruleId: "ja-no-abusage", + expectedMessages: ["可変する", "適用"], + }, + ]; + + const getViolations = (ruleId: string) => { + return textlintResult.messages.filter( + (message) => message.ruleId === `ja-technical-writing/${ruleId}`, + ); + }; + + // Single message tests + for (const { ruleId, expectedMessage } of ruleTests) { + it(`should detect ${ruleId} violations`, () => { + const violations = getViolations(ruleId); + expect(violations.length).toBeGreaterThan(0); + expect(violations[0].message).toContain(expectedMessage); + }); + } + + // Multi-message tests + for (const { ruleId, expectedMessages } of multiMessageTests) { + it(`should detect ${ruleId} violations`, () => { + const violations = getViolations(ruleId); + expect(violations.length).toBeGreaterThan(0); + + for (const expectedMessage of expectedMessages) { + expect( + violations.some((v) => v.message.includes(expectedMessage)), + ).toBe(true); + } + }); + } + }); + }); +}); diff --git a/test/example.typ b/test/unit/example.typ similarity index 100% rename from test/example.typ rename to test/unit/example.typ diff --git a/test/fixtures/Break/input.typ b/test/unit/fixtures/Break/input.typ similarity index 100% rename from test/fixtures/Break/input.typ rename to test/unit/fixtures/Break/input.typ diff --git a/test/fixtures/Break/output.json b/test/unit/fixtures/Break/output.json similarity index 100% rename from test/fixtures/Break/output.json rename to test/unit/fixtures/Break/output.json diff --git a/test/fixtures/Code/input.typ b/test/unit/fixtures/Code/input.typ similarity index 100% rename from test/fixtures/Code/input.typ rename to test/unit/fixtures/Code/input.typ diff --git a/test/fixtures/Code/output.json b/test/unit/fixtures/Code/output.json similarity index 100% rename from test/fixtures/Code/output.json rename to test/unit/fixtures/Code/output.json diff --git a/test/fixtures/CodeBlock/input.typ b/test/unit/fixtures/CodeBlock/input.typ similarity index 100% rename from test/fixtures/CodeBlock/input.typ rename to test/unit/fixtures/CodeBlock/input.typ diff --git a/test/fixtures/CodeBlock/output.json b/test/unit/fixtures/CodeBlock/output.json similarity index 100% rename from test/fixtures/CodeBlock/output.json rename to test/unit/fixtures/CodeBlock/output.json diff --git a/test/fixtures/Comment/input.typ b/test/unit/fixtures/Comment/input.typ similarity index 100% rename from test/fixtures/Comment/input.typ rename to test/unit/fixtures/Comment/input.typ diff --git a/test/fixtures/Comment/output.json b/test/unit/fixtures/Comment/output.json similarity index 100% rename from test/fixtures/Comment/output.json rename to test/unit/fixtures/Comment/output.json diff --git a/test/fixtures/Emphasis/input.typ b/test/unit/fixtures/Emphasis/input.typ similarity index 100% rename from test/fixtures/Emphasis/input.typ rename to test/unit/fixtures/Emphasis/input.typ diff --git a/test/fixtures/Emphasis/output.json b/test/unit/fixtures/Emphasis/output.json similarity index 100% rename from test/fixtures/Emphasis/output.json rename to test/unit/fixtures/Emphasis/output.json diff --git a/test/fixtures/Header/input.typ b/test/unit/fixtures/Header/input.typ similarity index 100% rename from test/fixtures/Header/input.typ rename to test/unit/fixtures/Header/input.typ diff --git a/test/fixtures/Header/output.json b/test/unit/fixtures/Header/output.json similarity index 100% rename from test/fixtures/Header/output.json rename to test/unit/fixtures/Header/output.json diff --git a/test/fixtures/Link/input.typ b/test/unit/fixtures/Link/input.typ similarity index 100% rename from test/fixtures/Link/input.typ rename to test/unit/fixtures/Link/input.typ diff --git a/test/fixtures/Link/output.json b/test/unit/fixtures/Link/output.json similarity index 100% rename from test/fixtures/Link/output.json rename to test/unit/fixtures/Link/output.json diff --git a/test/fixtures/Strong/input.typ b/test/unit/fixtures/Strong/input.typ similarity index 100% rename from test/fixtures/Strong/input.typ rename to test/unit/fixtures/Strong/input.typ diff --git a/test/fixtures/Strong/output.json b/test/unit/fixtures/Strong/output.json similarity index 100% rename from test/fixtures/Strong/output.json rename to test/unit/fixtures/Strong/output.json diff --git a/test/fixtures/empty-file/input.typ b/test/unit/fixtures/empty-file/input.typ similarity index 100% rename from test/fixtures/empty-file/input.typ rename to test/unit/fixtures/empty-file/input.typ diff --git a/test/fixtures/empty-file/output.json b/test/unit/fixtures/empty-file/output.json similarity index 100% rename from test/fixtures/empty-file/output.json rename to test/unit/fixtures/empty-file/output.json diff --git a/test/fixtures/start-with-single-newline/input.typ b/test/unit/fixtures/start-with-single-newline/input.typ similarity index 100% rename from test/fixtures/start-with-single-newline/input.typ rename to test/unit/fixtures/start-with-single-newline/input.typ diff --git a/test/fixtures/start-with-single-newline/output.json b/test/unit/fixtures/start-with-single-newline/output.json similarity index 100% rename from test/fixtures/start-with-single-newline/output.json rename to test/unit/fixtures/start-with-single-newline/output.json diff --git a/test/paragraphizedTextlintAstObject.json b/test/unit/paragraphizedTextlintAstObject.json similarity index 100% rename from test/paragraphizedTextlintAstObject.json rename to test/unit/paragraphizedTextlintAstObject.json diff --git a/test/parsing.test.ts b/test/unit/parsing.test.ts similarity index 89% rename from test/parsing.test.ts rename to test/unit/parsing.test.ts index 7ac485d..a071371 100644 --- a/test/parsing.test.ts +++ b/test/unit/parsing.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; // parse all fixture and should has import { test } from "@textlint/ast-tester"; import { describe, expect, it } from "vitest"; -import { convertTypstSourceToTextlintAstObject } from "../src/typstToTextlintAst"; +import { convertTypstSourceToTextlintAstObject } from "../../src/typstToTextlintAst"; describe("parsing", () => { const fixtureDir = path.join(__dirname, "fixtures"); diff --git a/test/rawTypstAstObject.json b/test/unit/rawTypstAstObject.json similarity index 100% rename from test/rawTypstAstObject.json rename to test/unit/rawTypstAstObject.json diff --git a/test/rawTypstAstString.txt b/test/unit/rawTypstAstString.txt similarity index 100% rename from test/rawTypstAstString.txt rename to test/unit/rawTypstAstString.txt diff --git a/test/textlintAstObject.json b/test/unit/textlintAstObject.json similarity index 100% rename from test/textlintAstObject.json rename to test/unit/textlintAstObject.json diff --git a/test/typstToTextlintAst.test.ts b/test/unit/typstToTextlintAst.test.ts similarity index 98% rename from test/typstToTextlintAst.test.ts rename to test/unit/typstToTextlintAst.test.ts index ddf366f..a565ee9 100644 --- a/test/typstToTextlintAst.test.ts +++ b/test/unit/typstToTextlintAst.test.ts @@ -9,7 +9,7 @@ import { extractRawSourceByLocation, getRawTypstAstString, paragraphizeTextlintAstObject, -} from "../src/typstToTextlintAst"; +} from "../../src/typstToTextlintAst"; const typstSource = fs.readFileSync( path.join(__dirname, "example.typ"), diff --git a/test/update-fixtures.ts b/test/unit/update-fixtures.ts similarity index 86% rename from test/update-fixtures.ts rename to test/unit/update-fixtures.ts index f609d49..acdedad 100644 --- a/test/update-fixtures.ts +++ b/test/unit/update-fixtures.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { test } from "@textlint/ast-tester"; -import { convertTypstSourceToTextlintAstObject } from "../src/typstToTextlintAst"; +import { convertTypstSourceToTextlintAstObject } from "../../src/typstToTextlintAst"; const fixtureDir = path.join(__dirname, "fixtures"); for (const filePath of fs.readdirSync(fixtureDir)) {