eslint-plugin-markdown-preferences 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Introduction
1
+ # eslint-plugin-markdown-preferences
2
2
 
3
- [eslint-plugin-markdown-preferences](https://www.npmjs.com/package/eslint-plugin-markdown-preferences) is ESLint plugin that enforces our markdown preferences.
3
+ A specialized ESLint plugin that helps enforce consistent writing style and formatting conventions in Markdown files. Perfect for documentation projects, blog posts, and any Markdown content where consistency matters.
4
4
 
5
5
  [![NPM license](https://img.shields.io/npm/l/eslint-plugin-markdown-preferences.svg)](https://www.npmjs.com/package/eslint-plugin-markdown-preferences)
6
6
  [![NPM version](https://img.shields.io/npm/v/eslint-plugin-markdown-preferences.svg)](https://www.npmjs.com/package/eslint-plugin-markdown-preferences)
@@ -13,15 +13,18 @@
13
13
 
14
14
  ## 📛 Features
15
15
 
16
- ESLint plugin that enforces our markdown preferences.
16
+ - **🔧 Auto-fixable rules** - Automatically format your Markdown files to match your style preferences
17
+ - **📝 Line break consistency** - Enforce consistent hard line break styles (backslash `\` vs trailing spaces)
18
+ - **🔗 Link enforcement** - Ensure specific words or terms are properly linked to their documentation
19
+ - **🎯 Customizable** - Configure rules to match your team's specific requirements
17
20
 
18
- You can check on the [Online DEMO](https://eslint-online-playground.netlify.app/#eslint-plugin-markdown-preferences).
21
+ **Try it live:** Check out the [Online Demo](https://eslint-online-playground.netlify.app/#eslint-plugin-markdown-preferences) to see the plugin in action!
19
22
 
20
23
  <!--DOCS_IGNORE_START-->
21
24
 
22
25
  ## 📖 Documentation
23
26
 
24
- See [documents](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/).
27
+ For detailed usage instructions, rule configurations, and examples, visit our comprehensive [documentation site](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/).
25
28
 
26
29
  ## 💿 Installation
27
30
 
@@ -0,0 +1,11 @@
1
+ //#region rolldown:runtime
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all) __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true
7
+ });
8
+ };
9
+
10
+ //#endregion
11
+ export { __export };
package/lib/index.d.ts CHANGED
@@ -1,76 +1,37 @@
1
- import * as _eslint_core from '@eslint/core';
2
- import { RuleDefinition } from '@eslint/core';
3
- import { Linter, ESLint } from 'eslint';
4
- import markdown from '@eslint/markdown';
5
-
6
- declare module 'eslint' {
7
- namespace Linter {
8
- interface RulesRecord extends RuleOptions {
9
- }
10
- }
11
- }
12
- interface RuleOptions {
13
- /**
14
- * enforce consistent hard linebreak style.
15
- * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html
16
- */
17
- 'markdown-preferences/hard-linebreak-style'?: Linter.RuleEntry<MarkdownPreferencesHardLinebreakStyle>;
18
- /**
19
- * enforce the specified word to be a link.
20
- * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html
21
- */
22
- 'markdown-preferences/prefer-linked-words'?: Linter.RuleEntry<MarkdownPreferencesPreferLinkedWords>;
1
+ import markdown from "@eslint/markdown";
2
+ import * as _eslint_core0 from "@eslint/core";
3
+ import { RuleDefinition } from "@eslint/core";
4
+ import { ESLint, Linter } from "eslint";
5
+
6
+ //#region src/configs/recommended.d.ts
7
+ declare namespace recommended_d_exports {
8
+ export { files, language, name$1 as name, plugins, rules$1 as rules };
23
9
  }
24
- type MarkdownPreferencesHardLinebreakStyle = [] | [
25
- {
26
- style?: ("backslash" | "spaces");
27
- }
28
- ];
29
- type MarkdownPreferencesPreferLinkedWords = [] | [
30
- {
31
- words: ({
32
- [k: string]: (string | null);
33
- } | string[]);
34
- [k: string]: unknown | undefined;
35
- }
36
- ];
37
-
38
10
  declare const name$1 = "markdown-preferences/recommended";
39
11
  declare const files: string[];
40
12
  declare const language = "markdown/commonmark";
41
13
  declare const plugins: {
42
- markdown: typeof markdown;
43
- readonly "markdown-preferences": ESLint.Plugin;
14
+ markdown: typeof markdown;
15
+ readonly "markdown-preferences": ESLint.Plugin;
44
16
  };
45
17
  declare const rules$1: Linter.RulesRecord;
46
-
47
- declare const recommended_files: typeof files;
48
- declare const recommended_language: typeof language;
49
- declare const recommended_plugins: typeof plugins;
50
- declare namespace recommended {
51
- export { recommended_files as files, recommended_language as language, name$1 as name, recommended_plugins as plugins, rules$1 as rules };
18
+ declare namespace meta_d_exports {
19
+ export { name, version };
52
20
  }
53
-
54
21
  declare const name: "eslint-plugin-markdown-preferences";
55
- declare const version: "0.1.0";
56
-
57
- declare const meta_name: typeof name;
58
- declare const meta_version: typeof version;
59
- declare namespace meta {
60
- export { meta_name as name, meta_version as version };
61
- }
62
-
22
+ declare const version: "0.1.1";
23
+ //#endregion
24
+ //#region src/index.d.ts
63
25
  declare const configs: {
64
- recommended: typeof recommended;
26
+ recommended: typeof recommended_d_exports;
65
27
  };
66
28
  declare const rules: Record<string, RuleDefinition>;
67
-
68
29
  declare const _default: {
69
- meta: typeof meta;
70
- configs: {
71
- recommended: typeof recommended;
72
- };
73
- rules: Record<string, RuleDefinition<_eslint_core.RuleDefinitionTypeOptions>>;
30
+ meta: typeof meta_d_exports;
31
+ configs: {
32
+ recommended: typeof recommended_d_exports;
33
+ };
34
+ rules: Record<string, RuleDefinition<_eslint_core0.RuleDefinitionTypeOptions>>;
74
35
  };
75
-
76
- export { configs, _default as default, meta, rules };
36
+ //#endregion
37
+ export { configs, _default as default, meta_d_exports as meta, rules };
package/lib/index.js CHANGED
@@ -1,251 +1,219 @@
1
- import path from 'path';
2
- import markdown from '@eslint/markdown';
1
+ import { __export } from "./chunk-Cl8Af3a2.js";
2
+ import path from "node:path";
3
+ import markdown from "@eslint/markdown";
3
4
 
4
- var __defProp = Object.defineProperty;
5
- var __export = (target, all) => {
6
- for (var name3 in all)
7
- __defProp(target, name3, { get: all[name3], enumerable: true });
8
- };
9
-
10
- // src/utils/index.ts
5
+ //#region src/utils/index.ts
6
+ /**
7
+ * Define the rule.
8
+ * @param ruleName ruleName
9
+ * @param rule rule module
10
+ */
11
11
  function createRule(ruleName, rule) {
12
- return {
13
- meta: {
14
- ...rule.meta,
15
- docs: {
16
- ...rule.meta.docs,
17
- url: `https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/${ruleName}.html`,
18
- ruleId: `markdown-preferences/${ruleName}`,
19
- ruleName
20
- }
21
- },
22
- create(context) {
23
- return rule.create(context);
24
- }
25
- };
12
+ return {
13
+ meta: {
14
+ ...rule.meta,
15
+ docs: {
16
+ ...rule.meta.docs,
17
+ url: `https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/${ruleName}.html`,
18
+ ruleId: `markdown-preferences/${ruleName}`,
19
+ ruleName
20
+ }
21
+ },
22
+ create(context) {
23
+ return rule.create(context);
24
+ }
25
+ };
26
26
  }
27
27
 
28
- // src/rules/hard-linebreak-style.ts
28
+ //#endregion
29
+ //#region src/rules/hard-linebreak-style.ts
29
30
  var hard_linebreak_style_default = createRule("hard-linebreak-style", {
30
- meta: {
31
- type: "layout",
32
- docs: {
33
- description: "enforce consistent hard linebreak style.",
34
- categories: ["recommended"]
35
- },
36
- fixable: "code",
37
- hasSuggestions: false,
38
- schema: [
39
- {
40
- type: "object",
41
- properties: {
42
- style: {
43
- enum: ["backslash", "spaces"]
44
- }
45
- },
46
- additionalProperties: false
47
- }
48
- ],
49
- messages: {
50
- expectedBackslash: "Expected a backslash linebreak.",
51
- expectedSpaces: "Expected a space linebreak."
52
- }
53
- },
54
- create(context) {
55
- const sourceCode = context.sourceCode;
56
- const linebreakStyle = context.options[0]?.style || "backslash";
57
- return {
58
- break: (node) => {
59
- const text = sourceCode.getText(node);
60
- if (linebreakStyle === "backslash" && !text.startsWith("\\")) {
61
- context.report({
62
- node,
63
- messageId: "expectedBackslash",
64
- fix(fixer) {
65
- return fixer.replaceText(node, "\\\n");
66
- }
67
- });
68
- } else if (linebreakStyle === "spaces" && !text.startsWith(" ")) {
69
- context.report({
70
- node,
71
- messageId: "expectedSpaces",
72
- fix(fixer) {
73
- return fixer.replaceText(node, " \n");
74
- }
75
- });
76
- }
77
- }
78
- };
79
- }
31
+ meta: {
32
+ type: "layout",
33
+ docs: {
34
+ description: "enforce consistent hard linebreak style.",
35
+ categories: ["recommended"]
36
+ },
37
+ fixable: "code",
38
+ hasSuggestions: false,
39
+ schema: [{
40
+ type: "object",
41
+ properties: { style: { enum: ["backslash", "spaces"] } },
42
+ additionalProperties: false
43
+ }],
44
+ messages: {
45
+ expectedBackslash: "Expected a backslash linebreak.",
46
+ expectedSpaces: "Expected a space linebreak."
47
+ }
48
+ },
49
+ create(context) {
50
+ const sourceCode = context.sourceCode;
51
+ const linebreakStyle = context.options[0]?.style || "backslash";
52
+ return { break: (node) => {
53
+ const text = sourceCode.getText(node);
54
+ if (linebreakStyle === "backslash" && !text.startsWith("\\")) context.report({
55
+ node,
56
+ messageId: "expectedBackslash",
57
+ fix(fixer) {
58
+ return fixer.replaceText(node, "\\\n");
59
+ }
60
+ });
61
+ else if (linebreakStyle === "spaces" && !text.startsWith(" ")) context.report({
62
+ node,
63
+ messageId: "expectedSpaces",
64
+ fix(fixer) {
65
+ return fixer.replaceText(node, " \n");
66
+ }
67
+ });
68
+ } };
69
+ }
80
70
  });
71
+
72
+ //#endregion
73
+ //#region src/rules/prefer-linked-words.ts
74
+ const RE_PUNCTUATOR = /^[\s,:]*$/u;
81
75
  var prefer_linked_words_default = createRule("prefer-linked-words", {
82
- meta: {
83
- type: "suggestion",
84
- docs: {
85
- description: "enforce the specified word to be a link.",
86
- categories: []
87
- },
88
- fixable: "code",
89
- hasSuggestions: false,
90
- schema: [
91
- {
92
- type: "object",
93
- properties: {
94
- words: {
95
- anyOf: [
96
- {
97
- type: "object",
98
- patternProperties: {
99
- "^[\\s\\S]+$": {
100
- type: ["string", "null"]
101
- }
102
- }
103
- },
104
- {
105
- type: "array",
106
- items: {
107
- type: "string"
108
- }
109
- }
110
- ]
111
- }
112
- },
113
- required: ["words"]
114
- }
115
- ],
116
- messages: {
117
- requireLink: 'The word "{{name}}" should be a link.'
118
- }
119
- },
120
- create(context) {
121
- const sourceCode = context.sourceCode;
122
- const words = context.options[0]?.words || {};
123
- const wordEntries = (Array.isArray(words) ? words.map((word) => [word, void 0]) : Object.entries(words)).map(
124
- ([word, link]) => [word, link ? adjustLink(link) : void 0]
125
- ).filter(([, link]) => link !== `./${path.basename(context.filename)}`);
126
- let ignore = null;
127
- return {
128
- "link, linkReference, heading"(node) {
129
- if (ignore) return;
130
- ignore = node;
131
- },
132
- "link, linkReference, heading:exit"(node) {
133
- if (ignore === node) ignore = null;
134
- },
135
- text(node) {
136
- if (ignore) return;
137
- for (const [word, link] of wordEntries) {
138
- let startPosition = 0;
139
- while (true) {
140
- const index = node.value.indexOf(word, startPosition);
141
- if (index < 0) break;
142
- startPosition = index + word.length;
143
- if ((node.value[index - 1] || "").trim() || (node.value[index + word.length] || "").trim()) {
144
- continue;
145
- }
146
- const loc = sourceCode.getLoc(node);
147
- const beforeLines = node.value.slice(0, index).split(/\n/u);
148
- const line = loc.start.line + beforeLines.length - 1;
149
- const column = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
150
- context.report({
151
- node,
152
- loc: {
153
- start: { line, column },
154
- end: { line, column: column + word.length }
155
- },
156
- messageId: "requireLink",
157
- data: {
158
- name: word
159
- },
160
- fix: link ? (fixer) => {
161
- const [start] = sourceCode.getRange(node);
162
- return fixer.replaceTextRange(
163
- [start + index, start + index + word.length],
164
- `[${word}](${link})`
165
- );
166
- } : null
167
- });
168
- }
169
- }
170
- },
171
- inlineCode(node) {
172
- if (ignore) return;
173
- for (const [word, link] of wordEntries) {
174
- if (node.value === word) {
175
- context.report({
176
- node,
177
- messageId: "requireLink",
178
- data: {
179
- name: word
180
- },
181
- fix: link ? (fixer) => {
182
- return fixer.replaceText(node, `[\`${word}\`](${link})`);
183
- } : null
184
- });
185
- }
186
- }
187
- }
188
- };
189
- function adjustLink(link) {
190
- if (/^\w+:/.test(link)) {
191
- return link;
192
- }
193
- if (link.startsWith("#")) {
194
- return link;
195
- }
196
- const absoluteLink = path.isAbsolute(link) ? link : path.join(context.cwd, link);
197
- return `./${path.relative(path.dirname(context.filename), absoluteLink)}`;
198
- }
199
- }
76
+ meta: {
77
+ type: "suggestion",
78
+ docs: {
79
+ description: "enforce the specified word to be a link.",
80
+ categories: []
81
+ },
82
+ fixable: "code",
83
+ hasSuggestions: false,
84
+ schema: [{
85
+ type: "object",
86
+ properties: { words: { anyOf: [{
87
+ type: "object",
88
+ patternProperties: { "^[\\s\\S]+$": { type: ["string", "null"] } }
89
+ }, {
90
+ type: "array",
91
+ items: { type: "string" }
92
+ }] } },
93
+ required: ["words"]
94
+ }],
95
+ messages: { requireLink: "The word \"{{name}}\" should be a link." }
96
+ },
97
+ create(context) {
98
+ const sourceCode = context.sourceCode;
99
+ const words = context.options[0]?.words || {};
100
+ const wordEntries = (Array.isArray(words) ? words.map((word) => [word, void 0]) : Object.entries(words)).map(([word, link]) => [word, link ? adjustLink(link) : void 0]).filter(([, link]) => link !== `./${path.basename(context.filename)}`);
101
+ let ignore = null;
102
+ return {
103
+ "link, linkReference, heading"(node) {
104
+ if (ignore) return;
105
+ ignore = node;
106
+ },
107
+ "link, linkReference, heading:exit"(node) {
108
+ if (ignore === node) ignore = null;
109
+ },
110
+ text(node) {
111
+ if (ignore) return;
112
+ const text = sourceCode.getText(node);
113
+ for (const [word, link] of wordEntries) {
114
+ let startPosition = 0;
115
+ while (true) {
116
+ const index = text.indexOf(word, startPosition);
117
+ if (index < 0) break;
118
+ startPosition = index + word.length;
119
+ if (!RE_PUNCTUATOR.test(text[index - 1] || "") || !RE_PUNCTUATOR.test(text[index + word.length] || "")) continue;
120
+ const loc = sourceCode.getLoc(node);
121
+ const beforeLines = text.slice(0, index).split(/\n/u);
122
+ const line = loc.start.line + beforeLines.length - 1;
123
+ const column = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
124
+ context.report({
125
+ node,
126
+ loc: {
127
+ start: {
128
+ line,
129
+ column
130
+ },
131
+ end: {
132
+ line,
133
+ column: column + word.length
134
+ }
135
+ },
136
+ messageId: "requireLink",
137
+ data: { name: word },
138
+ fix: link ? (fixer) => {
139
+ const [start] = sourceCode.getRange(node);
140
+ return fixer.replaceTextRange([start + index, start + index + word.length], `[${word}](${link})`);
141
+ } : null
142
+ });
143
+ }
144
+ }
145
+ },
146
+ inlineCode(node) {
147
+ if (ignore) return;
148
+ for (const [word, link] of wordEntries) if (node.value === word) context.report({
149
+ node,
150
+ messageId: "requireLink",
151
+ data: { name: word },
152
+ fix: link ? (fixer) => {
153
+ return fixer.replaceText(node, `[\`${word}\`](${link})`);
154
+ } : null
155
+ });
156
+ }
157
+ };
158
+ /**
159
+ * Adjust link to be relative to the file.
160
+ */
161
+ function adjustLink(link) {
162
+ if (/^\w+:/.test(link)) return link;
163
+ if (link.startsWith("#")) return link;
164
+ const absoluteLink = path.isAbsolute(link) || path.posix.isAbsolute(link) ? link : path.join(context.cwd, link);
165
+ return `./${path.relative(path.dirname(context.filename), absoluteLink)}`;
166
+ }
167
+ }
200
168
  });
201
169
 
202
- // src/utils/rules.ts
203
- var rules = [hard_linebreak_style_default, prefer_linked_words_default];
170
+ //#endregion
171
+ //#region src/utils/rules.ts
172
+ const rules$1 = [hard_linebreak_style_default, prefer_linked_words_default];
204
173
 
205
- // src/configs/recommended.ts
174
+ //#endregion
175
+ //#region src/configs/recommended.ts
206
176
  var recommended_exports = {};
207
177
  __export(recommended_exports, {
208
- files: () => files,
209
- language: () => language,
210
- name: () => name,
211
- plugins: () => plugins,
212
- rules: () => rules2
178
+ files: () => files,
179
+ language: () => language,
180
+ name: () => name$1,
181
+ plugins: () => plugins,
182
+ rules: () => rules$2
213
183
  });
214
- var name = "markdown-preferences/recommended";
215
- var files = ["**/*.md"];
216
- var language = "markdown/commonmark";
217
- var plugins = {
218
- markdown,
219
- // eslint-disable-next-line @typescript-eslint/naming-convention -- ignore
220
- get "markdown-preferences"() {
221
- return index_default;
222
- }
223
- };
224
- var rules2 = {
225
- // eslint-plugin-markdown-preferences rules
226
- "markdown-preferences/hard-linebreak-style": "error"
184
+ const name$1 = "markdown-preferences/recommended";
185
+ const files = ["**/*.md"];
186
+ const language = "markdown/commonmark";
187
+ const plugins = {
188
+ markdown,
189
+ get "markdown-preferences"() {
190
+ return src_default;
191
+ }
227
192
  };
193
+ const rules$2 = { "markdown-preferences/hard-linebreak-style": "error" };
228
194
 
229
- // src/meta.ts
195
+ //#endregion
196
+ //#region src/meta.ts
230
197
  var meta_exports = {};
231
198
  __export(meta_exports, {
232
- name: () => name2,
233
- version: () => version
199
+ name: () => name,
200
+ version: () => version
234
201
  });
235
- var name2 = "eslint-plugin-markdown-preferences";
236
- var version = "0.1.0";
202
+ const name = "eslint-plugin-markdown-preferences";
203
+ const version = "0.1.1";
237
204
 
238
- // src/index.ts
239
- var configs = {
240
- recommended: recommended_exports
205
+ //#endregion
206
+ //#region src/index.ts
207
+ const configs = { recommended: recommended_exports };
208
+ const rules = rules$1.reduce((obj, r) => {
209
+ obj[r.meta.docs.ruleName] = r;
210
+ return obj;
211
+ }, {});
212
+ var src_default = {
213
+ meta: meta_exports,
214
+ configs,
215
+ rules
241
216
  };
242
- var rules3 = rules.reduce(
243
- (obj, r) => {
244
- obj[r.meta.docs.ruleName] = r;
245
- return obj;
246
- },
247
- {}
248
- );
249
- var index_default = { meta: meta_exports, configs, rules: rules3 };
250
217
 
251
- export { configs, index_default as default, meta_exports as meta, rules3 as rules };
218
+ //#endregion
219
+ export { configs, src_default as default, meta_exports as meta, rules };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,9 +16,9 @@
16
16
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
17
17
  },
18
18
  "scripts": {
19
- "build": "npm run build:meta && npm run build:tsup",
19
+ "build": "npm run build:meta && npm run build:tsdown",
20
20
  "build:meta": "npm run ts -- ./tools/update-meta.ts",
21
- "build:tsup": "tsup",
21
+ "build:tsdown": "tsdown",
22
22
  "lint": "eslint .",
23
23
  "tsc": "tsc --project tsconfig.build.json",
24
24
  "eslint-fix": "eslint . --fix",
@@ -30,7 +30,7 @@
30
30
  "docs:watch": "vitepress dev docs --open",
31
31
  "docs:build": "vitepress build docs",
32
32
  "ts": "node --import=tsx",
33
- "mocha": "npm run ts ./node_modules/mocha/bin/mocha.js",
33
+ "mocha": "npm run ts -- ./node_modules/mocha/bin/mocha.js",
34
34
  "generate:version": "env-cmd -e version npm run update && npm run lint -- --fix",
35
35
  "changeset:version": "changeset version && npm run generate:version && git add --all",
36
36
  "changeset:publish": "npm run build && changeset publish"
@@ -54,10 +54,9 @@
54
54
  "url": "https://github.com/ota-meshi/eslint-plugin-markdown-preferences/issues"
55
55
  },
56
56
  "homepage": "https://ota-meshi.github.io/eslint-plugin-markdown-preferences/",
57
- "dependencies": {},
58
57
  "peerDependencies": {
59
- "eslint": ">=9.0.0",
60
- "@eslint/markdown": "^7.1.0"
58
+ "@eslint/markdown": "^7.1.0",
59
+ "eslint": ">=9.0.0"
61
60
  },
62
61
  "devDependencies": {
63
62
  "@changesets/changelog-github": "^0.5.1",
@@ -107,7 +106,7 @@
107
106
  "stylelint-config-recommended-vue": "^1.6.0",
108
107
  "stylelint-config-standard": "^39.0.0",
109
108
  "stylelint-config-standard-vue": "^1.0.0",
110
- "tsup": "^8.4.0",
109
+ "tsdown": "^0.13.1",
111
110
  "tsx": "^4.19.3",
112
111
  "twoslash-eslint": "^0.3.1",
113
112
  "type-fest": "^4.37.0",