diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 9289a3e13c3..9acffaed0a2 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -13,5 +13,5 @@ jobs: with: check_filenames: true check_hidden: true - skip: ./.git,*.png,*.csv,./archive,./legacy_submissions + skip: ./.git,./package.json,./package-lock.json,*.png,*.csv,./archive,./legacy_submissions ignore_words_file: './.codespellignore' diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/markdownlint.yml index 2e0b3deb895..76fe0052bce 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/markdownlint.yml @@ -6,7 +6,7 @@ on: - '!*' - '!archive/**' - '!templates/**' - - '!markdownlint/docs/**' + - '!markdownlint/**' - '!.github/**' jobs: @@ -23,7 +23,7 @@ jobs: !* !archive/** !templates/** - !markdownlint/docs/** + !markdownlint/** !.github/** separator: ',' - uses: DavidAnson/markdownlint-cli2-action@v14 diff --git a/.github/workflows/markdownlint_testing.yml b/.github/workflows/markdownlint_testing.yml new file mode 100644 index 00000000000..e3322423ca5 --- /dev/null +++ b/.github/workflows/markdownlint_testing.yml @@ -0,0 +1,15 @@ +name: MarkdownLint +on: + pull_request: + paths: + - 'markdownlint/**' + - '!markdownlint/docs/**' + +jobs: + test_custom_rules: + name: Test custom rules + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - run: npm install + - run: npm run test diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index e3b03c6969e..efdcdd66191 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -82,7 +82,10 @@ }, // link-fragments // Disabled as it uses a different heading conversion algo to the TOP website - "MD051": false + "MD051": false, + // descriptive-link-text + // Disabled and overridden by TOP001 custom rule + "MD059": false }, // Custom rules specific to the project // Docs for each rule can be found in `./markdownlint/docs` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index feaff20bee9..00f30433d7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,9 @@ Before submitting a PR for any lesson, you must also use our [Lesson Preview Too ## Curriculum Linting +> [!NOTE] +> For information about contributing to our custom linting rules, please read the [custom markdown linting contributing guide](./markdownlint/docs/README.md). + To help enforce the layout specified in our layout style guide, we use [markdownlint](https://github.com/DavidAnson/markdownlint). Whenever a PR is opened or has updates made to it, a workflow will run to check any files changed in the PR against common rules as well as custom rules specific to TOP. To make the workflow easier, we also strongly suggest that users who have a local clone run this linter locally before committing any changes. There are 2 ways you can do so: 1. Install the [Markdownlint VSCode Plugin](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint). This plugin will automatically pick up our markdownlint configuration and flag issues with a squiggly underline. diff --git a/markdownlint/TOP001_descriptiveLinkTextLabels/TOP001_descriptiveLinkTextLabels.js b/markdownlint/TOP001_descriptiveLinkTextLabels/TOP001_descriptiveLinkTextLabels.js index dcff6252390..6677b3b10ba 100644 --- a/markdownlint/TOP001_descriptiveLinkTextLabels/TOP001_descriptiveLinkTextLabels.js +++ b/markdownlint/TOP001_descriptiveLinkTextLabels/TOP001_descriptiveLinkTextLabels.js @@ -60,7 +60,7 @@ module.exports = { onError({ lineNumber: tokensAfterLinkOpen[0].lineNumber, detail: containsThisOrHere - ? `Expected text to not include the words "this" or "here". Use a more descriptive text that clearly conveys the purpose or content of the link.` + ? `Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.` : `"${linkContentString}" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.`, context: `[${linkContentString}](${linkUrl})`, }); diff --git a/markdownlint/TOP001_descriptiveLinkTextLabels/tests/TOP001.test.js b/markdownlint/TOP001_descriptiveLinkTextLabels/tests/TOP001.test.js new file mode 100644 index 00000000000..311121e6e31 --- /dev/null +++ b/markdownlint/TOP001_descriptiveLinkTextLabels/tests/TOP001.test.js @@ -0,0 +1,56 @@ +const { join } = require("node:path"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const rule = require("../TOP001_descriptiveLinkTextLabels"); + +const pathInRepo = "markdownlint/TOP001_descriptiveLinkTextLabels/tests"; +const expected = { + name: "TOP001/descriptive-link-text-labels", + description: "Links must have descriptive text labels", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP001.md", + ), +}; + +describe("TOP001", () => { + it("Links to the TOP001 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it('Flags link text labels containing "this" or "here"', async () => { + const filePath = "./this_or_here.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:23 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[this](someURL)"]`, + `${errorPath}:24 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[This video](someURL)"]`, + `${errorPath}:25 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[here](someURL)"]`, + `${errorPath}:26 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[Click here](someURL)"]`, + `${errorPath}:27 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[This other thing](someURL)"]`, + `${errorPath}:28 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[This blog post about flex-grow will be flagged as a false positive, but could still be updated](someURL)"]`, + `${errorPath}:29 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[This will get caught](someURL)"]`, + `${errorPath}:29 error ${expected.name} ${expected.description} [Expected text to not include the words "this" or "here". Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[this as separate matches](someURL)"]`, + ]); + }); + + it("Flags blacklisted link text labels", async () => { + const filePath = "./blacklisted_label_text.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:23 error ${expected.name} ${expected.description} ["video" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[video](someURL)"]`, + `${errorPath}:24 error ${expected.name} ${expected.description} ["videos" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[videos](someURL)"]`, + `${errorPath}:25 error ${expected.name} ${expected.description} ["a video" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[a video](someURL)"]`, + `${errorPath}:26 error ${expected.name} ${expected.description} ["docs" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[docs](someURL)"]`, + `${errorPath}:27 error ${expected.name} ${expected.description} ["the documentation" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[the documentation](someURL)"]`, + `${errorPath}:28 error ${expected.name} ${expected.description} ["their documentation" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[their documentation](someURL)"]`, + `${errorPath}:29 error ${expected.name} ${expected.description} ["page" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[page](someURL)"]`, + `${errorPath}:30 error ${expected.name} ${expected.description} ["their homepage" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[their homepage](someURL)"]`, + `${errorPath}:31 error ${expected.name} ${expected.description} ["playlist" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[playlist](someURL)"]`, + `${errorPath}:32 error ${expected.name} ${expected.description} ["a playlist" is not sufficiently descriptive by itself. Use a more descriptive label that clearly conveys the purpose or content of the link.] [Context: "[a playlist](someURL)"]`, + ]); + }); +}); diff --git a/markdownlint/TOP001_descriptiveLinkTextLabels/tests/blacklisted_label_text.md b/markdownlint/TOP001_descriptiveLinkTextLabels/tests/blacklisted_label_text.md new file mode 100644 index 00000000000..44ee828725f --- /dev/null +++ b/markdownlint/TOP001_descriptiveLinkTextLabels/tests/blacklisted_label_text.md @@ -0,0 +1,52 @@ +### Introduction + +Text content + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### The following links should NOT be flagged + +[Some descriptive link text](someURL) +[He replied](someURL) +[With is](someURL) +[Where](someURL) +[Heresy](someURL) +[Some text with the word video](someURL) +[Some text with the words documentation, an article, docs, the docs, page, a video, articles, resource, their docs](someURL) + +### The following links SHOULD be flagged as blacklisted text labels + +[video](someURL) +[videos](someURL) +[a video](someURL) +[docs](someURL) +[the documentation](someURL) +[their documentation](someURL) +[page](someURL) +[their homepage](someURL) +[playlist](someURL) +[a playlist](someURL) + +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP001_descriptiveLinkTextLabels/tests/TOP001_test.md b/markdownlint/TOP001_descriptiveLinkTextLabels/tests/this_or_here.md similarity index 83% rename from markdownlint/TOP001_descriptiveLinkTextLabels/tests/TOP001_test.md rename to markdownlint/TOP001_descriptiveLinkTextLabels/tests/this_or_here.md index a7dcec4a5f0..a35f00fa882 100644 --- a/markdownlint/TOP001_descriptiveLinkTextLabels/tests/TOP001_test.md +++ b/markdownlint/TOP001_descriptiveLinkTextLabels/tests/this_or_here.md @@ -18,20 +18,10 @@ This section contains a general overview of topics that you will learn in this l [Some text with the word video](someURL) [Some text with the words documentation, an article, docs, the docs, page, a video, articles, resource, their docs](someURL) -### The following links SHOULD be flagged +### The following links SHOULD be flagged for containing "this" or "here" [this](someURL) [This video](someURL) -[video](someURL) -[videos](someURL) -[a video](someURL) -[docs](someURL) -[the documentation](someURL) -[their documentation](someURL) -[page](someURL) -[their homepage](someURL) -[playlist](someURL) -[a playlist](someURL) [here](someURL) [Click here](someURL) [This other thing](someURL) diff --git a/markdownlint/TOP002_noCodeInHeadings/tests/TOP002.test.js b/markdownlint/TOP002_noCodeInHeadings/tests/TOP002.test.js new file mode 100644 index 00000000000..c8d4d2d0803 --- /dev/null +++ b/markdownlint/TOP002_noCodeInHeadings/tests/TOP002.test.js @@ -0,0 +1,55 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP002_noCodeInHeadings"); + +const pathInRepo = "markdownlint/TOP002_noCodeInHeadings/tests"; +const expected = { + name: "TOP002/no-code-headings", + description: "No inline code in headings", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP002.md", + ), +}; + +describe("TOP002", () => { + describe("Lint", () => { + it("Links to the TOP002 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags inline code in headings", async () => { + const filePath = "test.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:15 error ${expected.name} ${expected.description} [Headings should not contain inline code.] [Context: "\`heading\`"]`, + `${errorPath}:19 error ${expected.name} ${expected.description} [Headings should not contain inline code.] [Context: "\`other heading\`"]`, + `${errorPath}:19 error ${expected.name} ${expected.description} [Headings should not contain inline code.] [Context: "\`flagged\`"]`, + ]); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP002 in the fixed test md file", async () => { + const file = "./fixed_test.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP002 errors`, + ); + }); + + it("Strips inline code blocks in headings", async () => { + const fixedFileContents = await fixLintErrors("./test.md"); + const correctFile = await readFile(join(__dirname, "./fixed_test.md")); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP002_noCodeInHeadings/tests/fixed_test.md b/markdownlint/TOP002_noCodeInHeadings/tests/fixed_test.md new file mode 100644 index 00000000000..5908673cbf1 --- /dev/null +++ b/markdownlint/TOP002_noCodeInHeadings/tests/fixed_test.md @@ -0,0 +1,39 @@ +### Introduction + +Text content. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### This heading should NOT be flagged + +Some more content. + +### This heading SHOULD be flagged + +Some content. + +### This other heading will get flagged twice + +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP002_noCodeInHeadings/tests/TOP002_test.md b/markdownlint/TOP002_noCodeInHeadings/tests/test.md similarity index 100% rename from markdownlint/TOP002_noCodeInHeadings/tests/TOP002_test.md rename to markdownlint/TOP002_noCodeInHeadings/tests/test.md diff --git a/markdownlint/TOP003_defaultSectionContent/TOP003_defaultSectionContent.js b/markdownlint/TOP003_defaultSectionContent/TOP003_defaultSectionContent.js index b19abf1606a..a817bafd424 100644 --- a/markdownlint/TOP003_defaultSectionContent/TOP003_defaultSectionContent.js +++ b/markdownlint/TOP003_defaultSectionContent/TOP003_defaultSectionContent.js @@ -85,7 +85,7 @@ function getListSectionErrors(sectionTokens, section) { const sectionStartsWithList = tokensAfterHeading[0].line.startsWith("- "); const errorDetail = sectionStartsWithList ? `Expect default content to precede unordered list of ${listItemsName}: "${listSectionsDefaultContent[section]}"` - : `Expected: "${listSectionsDefaultContent[section]}"; Actual: "${tokensAfterHeading[0].line}",`; + : `Expected: "${listSectionsDefaultContent[section]}"; Actual: "${tokensAfterHeading[0].line}"`; let replacementText = listSectionsDefaultContent[section]; if (sectionStartsWithList) { diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003.test.js b/markdownlint/TOP003_defaultSectionContent/tests/TOP003.test.js new file mode 100644 index 00000000000..048f1f55da5 --- /dev/null +++ b/markdownlint/TOP003_defaultSectionContent/tests/TOP003.test.js @@ -0,0 +1,156 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP003_defaultSectionContent"); + +const pathInRepo = "markdownlint/TOP003_defaultSectionContent/tests"; +const expected = { + name: "TOP003/default-section-content", + description: "Sections have default content", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP003.md", + ), +}; + +describe("TOP003", () => { + describe("Lint", () => { + it("Links to the TOP003 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags when extra content surrounds default section lists", async () => { + const filePath = "./content_around_list.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:9 error ${expected.name} ${expected.description} [Only an unordered list of lesson overviews can follow the default content.]`, + `${errorPath}:13 error ${expected.name} ${expected.description} [There should be no additional content after the unordered list of lesson overviews]`, + ]); + }); + + it("Flags when sections with default content are empty", async () => { + const filePath = "./empty_section.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:5 error ${expected.name} ${expected.description} [The lesson overview section cannot be empty]`, + `${errorPath}:19 error ${expected.name} ${expected.description} [The knowledge check section cannot be empty]`, + `${errorPath}:21 error ${expected.name} ${expected.description} [The additional resources section cannot be empty]`, + ]); + }); + + it("Flags when sections have incorrect default content", async () => { + const filePath = "./incorrect_content.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:7 error ${expected.name} ${expected.description} [Expected: "This section contains a general overview of topics that you will learn in this lesson."; Actual: "This section has the wrong text following the heading that should flag an error."]`, + `${errorPath}:25 error ${expected.name} ${expected.description} [Expect default content to precede unordered list of knowledge checks: "The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge."]`, + ]); + }); + + it("Flags when a section does not have a required unordered list", async () => { + const filePath = "./missing_list.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:7 error ${expected.name} ${expected.description} [Must include an unordered list of lesson overviews in the "lesson overview" section]`, + `${errorPath}:23 error ${expected.name} ${expected.description} [Must include an unordered list of knowledge checks in the "knowledge check" section]`, + `${errorPath}:27 error ${expected.name} ${expected.description} [Must include an unordered list of additional resources in the "additional resources" section]`, + ]); + }); + + it("Flags when the assignment section is missing the necessary div wrapper", async () => { + const filePath = "./missing_wrapper.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:15 error ${expected.name} ${expected.description} [Assignment sections must include an HTML div element with class="lesson-content__panel" and markdown="1" attributes]`, + ]); + }); + + it("Flags when ordered list used instead of unordered list", async () => { + const filePath = "./ordered_list.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:27 error ${expected.name} ${expected.description} [The knowledge check section must not include any ordered lists.]`, + `${errorPath}:27 error ${expected.name} ${expected.description} [Must include an unordered list of knowledge checks in the "knowledge check" section]`, + `${errorPath}:28 error ${expected.name} ${expected.description} [The knowledge check section must not include any ordered lists.]`, + ]); + }); + + it("Flags when list section contains a nested list", async () => { + const filePath = "./nested_list.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:10 error ${expected.name} ${expected.description} [The lesson overview section must not contain nested lists.]`, + `${errorPath}:36 error ${expected.name} ${expected.description} [The additional resources section must not contain nested lists.]`, + ]); + }); + + it("Does not flag any errors if no violations", async () => { + const filePath = "./valid.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + }); + + describe("Fix", () => { + it("Does not flag any TOP003 errors in any fixed test md file", async () => { + const fixedTestMarkdownFiles = [ + "./fixed_ordered_list.md", + "./fixed_incorrect_content.md", + "./fixed_content_around_list.md", + ]; + + for (const file of fixedTestMarkdownFiles) { + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP003 errors`, + ); + } + }); + + it("Converts ordered lists to unordered lists", async () => { + const fixedFileContents = await fixLintErrors("./ordered_list.md"); + const correctFile = await readFile( + join(__dirname, "./fixed_ordered_list.md"), + ); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + + it("Inserts/replaces missing or incorrect default section content", async () => { + const fixedFileContents = await fixLintErrors("./incorrect_content.md"); + const correctFile = await readFile( + join(__dirname, "./fixed_incorrect_content.md"), + ); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + + it("Removes flagged content around default section lists", async () => { + const fixedFileContents = await fixLintErrors("./content_around_list.md"); + const correctFile = await readFile( + join(__dirname, "./fixed_content_around_list.md"), + ); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_content-around-list.md b/markdownlint/TOP003_defaultSectionContent/tests/content_around_list.md similarity index 100% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_content-around-list.md rename to markdownlint/TOP003_defaultSectionContent/tests/content_around_list.md diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_empty-section.md b/markdownlint/TOP003_defaultSectionContent/tests/empty_section.md similarity index 100% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_empty-section.md rename to markdownlint/TOP003_defaultSectionContent/tests/empty_section.md diff --git a/markdownlint/TOP003_defaultSectionContent/tests/fixed_content_around_list.md b/markdownlint/TOP003_defaultSectionContent/tests/fixed_content_around_list.md new file mode 100644 index 00000000000..536f51dc867 --- /dev/null +++ b/markdownlint/TOP003_defaultSectionContent/tests/fixed_content_around_list.md @@ -0,0 +1,35 @@ +### Introduction + +Text content + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + + +- LO item. + + +### Custom section + +Text content + +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP003_defaultSectionContent/tests/fixed_incorrect_content.md b/markdownlint/TOP003_defaultSectionContent/tests/fixed_incorrect_content.md new file mode 100644 index 00000000000..daff977f7b9 --- /dev/null +++ b/markdownlint/TOP003_defaultSectionContent/tests/fixed_incorrect_content.md @@ -0,0 +1,33 @@ +### Introduction + +Text content + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Custom section + +Text content + +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item that should flag an error + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP003_defaultSectionContent/tests/fixed_ordered_list.md b/markdownlint/TOP003_defaultSectionContent/tests/fixed_ordered_list.md new file mode 100644 index 00000000000..ceba6c3a7f1 --- /dev/null +++ b/markdownlint/TOP003_defaultSectionContent/tests/fixed_ordered_list.md @@ -0,0 +1,34 @@ +### Introduction + +Text content + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- An item. + +### Custom section + +Text content + +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item that should flag an error +- Another KC item that should flag an error + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_incorrect-content.md b/markdownlint/TOP003_defaultSectionContent/tests/incorrect_content.md similarity index 100% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_incorrect-content.md rename to markdownlint/TOP003_defaultSectionContent/tests/incorrect_content.md diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_missing-list.md b/markdownlint/TOP003_defaultSectionContent/tests/missing_list.md similarity index 100% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_missing-list.md rename to markdownlint/TOP003_defaultSectionContent/tests/missing_list.md diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_missing-wrapper.md b/markdownlint/TOP003_defaultSectionContent/tests/missing_wrapper.md similarity index 100% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_missing-wrapper.md rename to markdownlint/TOP003_defaultSectionContent/tests/missing_wrapper.md diff --git a/markdownlint/TOP003_defaultSectionContent/tests/nested_list.md b/markdownlint/TOP003_defaultSectionContent/tests/nested_list.md new file mode 100644 index 00000000000..a82c4b932f6 --- /dev/null +++ b/markdownlint/TOP003_defaultSectionContent/tests/nested_list.md @@ -0,0 +1,36 @@ +### Introduction + +Text content + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- An item. + - A nested item that should flag an error. +- Unnested list item. + +### Custom section + +Text content + +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item + - Nested AR item that should flag an error. diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_nested-and-ordered-list.md b/markdownlint/TOP003_defaultSectionContent/tests/ordered_list.md similarity index 91% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_nested-and-ordered-list.md rename to markdownlint/TOP003_defaultSectionContent/tests/ordered_list.md index 5fbe3ff29b5..4780ccbec35 100644 --- a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_nested-and-ordered-list.md +++ b/markdownlint/TOP003_defaultSectionContent/tests/ordered_list.md @@ -7,8 +7,6 @@ Text content This section contains a general overview of topics that you will learn in this lesson. - An item. - - A nested item that should flag an error. -- Unnested list item. ### Custom section diff --git a/markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_valid.md b/markdownlint/TOP003_defaultSectionContent/tests/valid.md similarity index 100% rename from markdownlint/TOP003_defaultSectionContent/tests/TOP003_test_valid.md rename to markdownlint/TOP003_defaultSectionContent/tests/valid.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/TOP004.test.js b/markdownlint/TOP004_lessonHeadings/tests/TOP004.test.js new file mode 100644 index 00000000000..c2a4a7273e1 --- /dev/null +++ b/markdownlint/TOP004_lessonHeadings/tests/TOP004.test.js @@ -0,0 +1,108 @@ +const { join } = require("node:path"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const rule = require("../TOP004_lessonHeadings"); + +const pathInRepo = "markdownlint/TOP004_lessonHeadings/tests"; +const expected = { + name: "TOP004/lesson-headings", + description: "Required heading structure", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP004.md", + ), +}; + +describe("TOP004", () => { + it("Links to the TOP004 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags when a required heading is missing", async () => { + const filePath = "./missing_heading.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:5 error ${expected.name} ${expected.description} [Expected: ### Lesson overview; Actual: ### Custom section]`, + ]); + }); + + it("Flags when a level-specific wildcard heading uses the wrong level", async () => { + const filePath = "./wrong_wildcard_level.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:21 error ${expected.name} ${expected.description} [Expected: h4 heading; Actual: h3 heading] [Context: "### An invalid wildcard heading"]`, + ]); + }); + + it("Does not flag any errors if no heading structure violations", async () => { + const filePath = "./valid_with_additional_resources.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + + it("Does not flag in a valid lesson file without additional resources", async () => { + const filePath = "./valid_no_additional_resources.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + + describe("Projects", () => { + it("Flags incorrect heading structure in a project", async () => { + const filePath = "./project_invalid.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:12 error ${expected.name} ${expected.description} [Missing heading (case sensitive): ### Assignment] [Context: "### Assignment"]`, + ]); + }); + + it("Does not flag a project if no heading structure violations", async () => { + const filePath = "./project_valid.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + }); + + describe("Lesson exceptions", () => { + it("Does not flag in a Conclusion lesson", async () => { + const filePath = "./conclusion.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + + it("Does not flag in a How This Course Will Work lesson", async () => { + const filePath = "./how_this_course_will_work.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + }); + + describe("Guides", () => { + it("Flags when first heading does not start correctly", async () => { + const filePath = "./_guides/guide_invalid.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:1 error ${expected.name} ${expected.description} [Expected: heading starting with "### Guide: "; Actual: ### Installation Guide]`, + ]); + }); + + it("Does not flag any errors if no violations", async () => { + const filePath = "./_guides/guide_valid.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + }); +}); diff --git a/markdownlint/TOP004_lessonHeadings/tests/_guides/guide_TOP004_test_invalid.md b/markdownlint/TOP004_lessonHeadings/tests/_guides/guide_invalid.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/_guides/guide_TOP004_test_invalid.md rename to markdownlint/TOP004_lessonHeadings/tests/_guides/guide_invalid.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/_guides/guide_TOP004_test_valid.md b/markdownlint/TOP004_lessonHeadings/tests/_guides/guide_valid.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/_guides/guide_TOP004_test_valid.md rename to markdownlint/TOP004_lessonHeadings/tests/_guides/guide_valid.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/TOP004_test_missing_heading.md b/markdownlint/TOP004_lessonHeadings/tests/missing_heading.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/TOP004_test_missing_heading.md rename to markdownlint/TOP004_lessonHeadings/tests/missing_heading.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/project_TOP004_test_invalid.md b/markdownlint/TOP004_lessonHeadings/tests/project_invalid.md similarity index 55% rename from markdownlint/TOP004_lessonHeadings/tests/project_TOP004_test_invalid.md rename to markdownlint/TOP004_lessonHeadings/tests/project_invalid.md index e6b35c8fd44..a87cacf843d 100644 --- a/markdownlint/TOP004_lessonHeadings/tests/project_TOP004_test_invalid.md +++ b/markdownlint/TOP004_lessonHeadings/tests/project_invalid.md @@ -1,6 +1,6 @@ ### Introduction -This file should not be flagged with any errors. +This file should flag a TOP004 error due to a missing assignment assignment section. ### Custom section diff --git a/markdownlint/TOP004_lessonHeadings/tests/project_TOP004_test_valid.md b/markdownlint/TOP004_lessonHeadings/tests/project_valid.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/project_TOP004_test_valid.md rename to markdownlint/TOP004_lessonHeadings/tests/project_valid.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/TOP004_test_valid_no_additional_resources.md b/markdownlint/TOP004_lessonHeadings/tests/valid_no_additional_resources.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/TOP004_test_valid_no_additional_resources.md rename to markdownlint/TOP004_lessonHeadings/tests/valid_no_additional_resources.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/TOP004_test_valid_with_additional_resources.md b/markdownlint/TOP004_lessonHeadings/tests/valid_with_additional_resources.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/TOP004_test_valid_with_additional_resources.md rename to markdownlint/TOP004_lessonHeadings/tests/valid_with_additional_resources.md diff --git a/markdownlint/TOP004_lessonHeadings/tests/TOP004_test_wrong_wildcard_level.md b/markdownlint/TOP004_lessonHeadings/tests/wrong_wildcard_level.md similarity index 100% rename from markdownlint/TOP004_lessonHeadings/tests/TOP004_test_wrong_wildcard_level.md rename to markdownlint/TOP004_lessonHeadings/tests/wrong_wildcard_level.md diff --git a/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/TOP005.test.js b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/TOP005.test.js new file mode 100644 index 00000000000..25f867d1471 --- /dev/null +++ b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/TOP005.test.js @@ -0,0 +1,75 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP005_blanksAroundMultilineHtmlTags"); + +const pathInRepo = "markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests"; +const expected = { + name: "TOP005/blanks-around-multiline-html-tags", + description: + "Multiline HTML tags should be surrounded by blank lines or code block delimiters", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP005.md", + ), +}; + +describe("TOP005", () => { + describe("Lint", () => { + it("Links to the TOP005 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags when markdown multiline HTML tags are not surrounded by blank lines or code block delimiters", async () => { + const filePath = "./flagged_tags.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:19 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) after the tag] [Context: "
"]`, + `${errorPath}:28 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag] [Context: "
"]`, + `${errorPath}:35 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) after the tag] [Context: "
"]`, + `${errorPath}:37 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag and after the tag] [Context: "
"]`, + `${errorPath}:38 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag and after the tag] [Context: "
"]`, + `${errorPath}:40 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag] [Context: "
"]`, + `${errorPath}:46 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag] [Context: "
"]`, + `${errorPath}:50 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) after the tag] [Context: "

"]`, + `${errorPath}:52 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag] [Context: "

"]`, + `${errorPath}:54 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) after the tag] [Context: "
"]`, + `${errorPath}:55 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag and after the tag] [Context: "

"]`, + `${errorPath}:57 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag and after the tag] [Context: "

"]`, + `${errorPath}:58 error ${expected.name} ${expected.description} [Expected a blank line or a code block delimiter (\`\`\`) before the tag] [Context: "
"]`, + ]); + }); + + it("Does not flag when no rule violations", async () => { + const filePath = "./ignored_tags.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP005 in the fixed test md file", async () => { + const file = "./fixed_flagged_tags.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP005 errors`, + ); + }); + + it("Wraps flagged multiline HTML tags with missing blank lines", async () => { + const fixedFileContents = await fixLintErrors("./flagged_tags.md"); + const correctFile = await readFile( + join(__dirname, "./fixed_flagged_tags.md"), + ); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/TOP005_test.md b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/fixed_flagged_tags.md similarity index 63% rename from markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/TOP005_test.md rename to markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/fixed_flagged_tags.md index b121539cffe..1e56868baa4 100644 --- a/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/TOP005_test.md +++ b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/fixed_flagged_tags.md @@ -12,17 +12,12 @@ This section contains a general overview of topics that you will learn in this l
-Valid div due to each tag being surrounded by blank lines. -
#### Custom section -
Valid single-line div
- -
Valid single-line div
Might even have other paragraph content with it. -
+ The opening tag is invalid due to not being surrounded by blank lines. Until a blank line is encountered, if there are any unrelated linting errors, the vast majority of them will not be caught due to how `markdown-it` parses `html_block` tokens. @@ -31,6 +26,7 @@ The closing tag is valid as it is surrounded by blank lines.
Non-empty/codeblock line +
The opening tag is invalid due to not being surrounded by blank lines or codeblock delimiters. @@ -39,82 +35,43 @@ The blank line after it does allow the linter to correctly flag and unrelated li
+ Also invalidates when HTML blocks are chained without blank lines between them. -
-
-Also invalidates when HTML blocks are chained without blank lines between them. +
-```markdown
-The only exception to blank lines is a code block delimiter. +Also invalidates when HTML blocks are chained without blank lines between them.
-``` ```markdown
This line above the closing tag is not a blank line nor a code block delimiter, so the closing tag errors. -
-``` -```html -
-

- Does not flag when used in an HTML example -

``` -```jsx +```markdown

- Also does not flag when used in JSX code blocks -

-``` -```erb -<%= if language.isErb? %> -

Also does not flag when used in erb code blocks

-<% end %> -``` + Flags such tags if not in an ignored code block (like HTML/JS/JSX etc.) -```ejs -<% if (isEjs) { %> -

Also does not flag when used in ejs code blocks

-<% } %> -``` - -```ruby -if ruby? - html_fragment = <<~HTML -

Does not flag when used in ruby code blocks

- HTML -end -``` - -```javascript -const htmlString = ` -

Does not flag when used in JavaScript code blocks, e.g. template literals.

-`; -``` - -```markdown -

- But does not like it if done in a non-HTML/JSX code block

+

+ TOP005 doesn't care it the tag is indented or not. +

+
``` - -### `Will not flag ignore comments which require being directly followed by the line to ignore` - ### Knowledge check The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. diff --git a/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/flagged_tags.md b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/flagged_tags.md new file mode 100644 index 00000000000..ae4bff78ac6 --- /dev/null +++ b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/flagged_tags.md @@ -0,0 +1,71 @@ +### Introduction + +This file should flag with TOP005 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Assignment + +
+ +
+ +#### Custom section + +
+The opening tag is invalid due to not being surrounded by blank lines. +Until a blank line is encountered, if there are any unrelated linting errors, the vast majority of them will not be caught due to how `markdown-it` parses `html_block` tokens. + +The closing tag is valid as it is surrounded by blank lines. + +
+ +Non-empty/codeblock line +
+ +The opening tag is invalid due to not being surrounded by blank lines or codeblock delimiters. +The blank line after it does allow the linter to correctly flag and unrelated linting errors in these lines if there are any. + +
+ +
+Also invalidates when HTML blocks are chained without blank lines between them. +
+
+Also invalidates when HTML blocks are chained without blank lines between them. +
+ +```markdown +
+ +This line above the closing tag is not a blank line nor a code block delimiter, so the closing tag errors. +
+``` + +```markdown +

+ Flags such tags if not in an ignored code block (like HTML/JS/JSX etc.) +

+ +
+

+ TOP005 doesn't care it the tag is indented or not. +

+
+``` + +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/ignored_tags.md b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/ignored_tags.md new file mode 100644 index 00000000000..e342c037857 --- /dev/null +++ b/markdownlint/TOP005_blanksAroundMultilineHtmlTags/tests/ignored_tags.md @@ -0,0 +1,86 @@ +### Introduction + +This file should not flag any errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Assignment + +
+ +Valid div due to each tag being surrounded by blank lines. + +
+ +#### Custom section + +
Valid single-line div
+ +
Valid single-line div
Might even have other paragraph content with it. + +```markdown +
+ +The only exception to blank lines is a code block delimiter. + +
+``` + +```html +
+

+ Does not flag when used in an HTML example +

+
+``` + +```jsx +

+ Also does not flag when used in JSX code blocks +

+``` + +```erb +<%= if language.isErb? %> +

Also does not flag when used in erb code blocks

+<% end %> +``` + +```ejs +<% if (isEjs) { %> +

Also does not flag when used in ejs code blocks

+<% } %> +``` + +```ruby +if ruby? + html_fragment = <<~HTML +

Does not flag when used in ruby code blocks

+ HTML +end +``` + +```javascript +const htmlString = ` +

Does not flag when used in JavaScript code blocks, e.g. template literals.

+`; +``` + + +### `Will not flag ignore comments which require being directly followed by the line to ignore` + +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP006_fullFencedCodeLanguage/TOP006_fullFencedCodeLanguage.js b/markdownlint/TOP006_fullFencedCodeLanguage/TOP006_fullFencedCodeLanguage.js index a8c93d01ef9..7bb930e56c3 100644 --- a/markdownlint/TOP006_fullFencedCodeLanguage/TOP006_fullFencedCodeLanguage.js +++ b/markdownlint/TOP006_fullFencedCodeLanguage/TOP006_fullFencedCodeLanguage.js @@ -42,7 +42,7 @@ module.exports = { fencesWithAbbreviatedName.forEach((fence) => { onError({ lineNumber: fence.lineNumber, - detail: `Expected: ${fence.fullName}; Actual: ${fence.abbreviatedName} `, + detail: `Expected: ${fence.fullName}; Actual: ${fence.abbreviatedName}`, context: fence.text, fixInfo: { editColumn: fence.languageStartingColumn, diff --git a/markdownlint/TOP006_fullFencedCodeLanguage/tests/TOP006.test.js b/markdownlint/TOP006_fullFencedCodeLanguage/tests/TOP006.test.js new file mode 100644 index 00000000000..3f983efdbea --- /dev/null +++ b/markdownlint/TOP006_fullFencedCodeLanguage/tests/TOP006.test.js @@ -0,0 +1,62 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP006_fullFencedCodeLanguage"); + +const pathInRepo = "markdownlint/TOP006_fullFencedCodeLanguage/tests"; +const expected = { + name: "TOP006/full-fenced-code-language", + description: + "Fenced code blocks must use the full name for a language if both full and abbreviated options are valid.", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP006.md", + ), +}; + +describe("TOP006", () => { + describe("Lint", () => { + it("Links to the TOP006 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags abbreviated code block languages", async () => { + const filePath = "./test.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:21 error ${expected.name} ${expected.description} [Expected: javascript; Actual: js] [Context: "\`\`\`js"]`, + `${errorPath}:26 error ${expected.name} ${expected.description} [Expected: javascript; Actual: js] [Context: "~~~js"]`, + `${errorPath}:45 error ${expected.name} ${expected.description} [Expected: ruby; Actual: rb] [Context: "\`\`\`rb"]`, + `${errorPath}:53 error ${expected.name} ${expected.description} [Expected: text; Actual: txt] [Context: "\`\`\`txt"]`, + `${errorPath}:57 error ${expected.name} ${expected.description} [Expected: yaml; Actual: yml] [Context: "\`\`\`yml"]`, + `${errorPath}:65 error ${expected.name} ${expected.description} [Expected: bash; Actual: sh] [Context: "\`\`\`sh"]`, + `${errorPath}:88 error ${expected.name} ${expected.description} [Expected: markdown; Actual: md] [Context: "\`\`\`\`md"]`, + `${errorPath}:89 error ${expected.name} ${expected.description} [Expected: javascript; Actual: js] [Context: "\`\`\`js"]`, + `${errorPath}:96 error ${expected.name} ${expected.description} [Expected: javascript; Actual: js] [Context: " \`\`\`js"]`, + ]); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP006 in the fixed test md file", async () => { + const file = "./fixed_test.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP006 errors`, + ); + }); + + it("Replaces abbreviated code block languages with full name", async () => { + const fixedFileContents = await fixLintErrors("./test.md"); + const correctFile = await readFile(join(__dirname, "./fixed_test.md")); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP006_fullFencedCodeLanguage/tests/fixed_test.md b/markdownlint/TOP006_fullFencedCodeLanguage/tests/fixed_test.md new file mode 100644 index 00000000000..24a566e33a0 --- /dev/null +++ b/markdownlint/TOP006_fullFencedCodeLanguage/tests/fixed_test.md @@ -0,0 +1,110 @@ +### Introduction + +This file should flag with TOP006 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Assignment + +
+ +Assignment + +
+ +#### Custom section + +```javascript +console.log("This code block should flag an error as it uses "js" instead of "javascript"."); +``` + + +~~~javascript +console.log("The rule will still flag even if tilde delimiters are used"); +~~~ + + +```javascript +console.log("This code block is valid as it uses the appropriate full name."); +``` + +```markdown +The rule catches the following languages, as they are they ones expected to be seen in this repo's files +md => markdown +rb => ruby +js => javascript +txt => text +sh => bash +yml => yaml +``` + +```ruby +puts "Example of rb flagging." +``` + +```ruby +puts "Use the full name!" +``` + +```text +As does txt. +``` + +```yaml +description: This will flag +``` + +```yaml +description: Unless you use the full name +``` + +```bash +prefer --bash-over-sh +``` + +```bash +like --this +``` + +```html +

HTML is not considered as only the abbreviated name is a valid option.

+

The same applies to similar languages like CSS and JSX.

+``` + +```css +.error { + display: none; +} +``` + +```jsx +{isExempt &&

No error here!

} +``` + +````markdown +```javascript +console.log("Flags abbreviated names even with nested code blocks."); +``` +```` + +1. List item + + ```javascript + console.log("Flags abbreviated names even with indented code blocks."); + ``` + +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP006_fullFencedCodeLanguage/tests/TOP006_test.md b/markdownlint/TOP006_fullFencedCodeLanguage/tests/test.md similarity index 97% rename from markdownlint/TOP006_fullFencedCodeLanguage/tests/TOP006_test.md rename to markdownlint/TOP006_fullFencedCodeLanguage/tests/test.md index ecc95f04431..eba4a0b5493 100644 --- a/markdownlint/TOP006_fullFencedCodeLanguage/tests/TOP006_test.md +++ b/markdownlint/TOP006_fullFencedCodeLanguage/tests/test.md @@ -12,7 +12,7 @@ This section contains a general overview of topics that you will learn in this l
-Valid div due to each tag being surrounded by blank lines. +Assignment
diff --git a/markdownlint/TOP007_useMarkdownLinks/tests/TOP007.test.js b/markdownlint/TOP007_useMarkdownLinks/tests/TOP007.test.js new file mode 100644 index 00000000000..fba76312666 --- /dev/null +++ b/markdownlint/TOP007_useMarkdownLinks/tests/TOP007.test.js @@ -0,0 +1,67 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP007_useMarkdownLinks"); + +const pathInRepo = "markdownlint/TOP007_useMarkdownLinks/tests"; +const expected = { + name: "TOP007/use-markdown-links", + description: + "Links used to navigate to external content or other landmarks in the page should use markdown links instead of HTML anchor tags.", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP007.md", + ), +}; + +describe("TOP007", () => { + describe("Lint", () => { + it("Links to the TOP007 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags non-markdown links used for markdown purposes", async () => { + const filePath = "./anchors_in_markdown.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:21 error ${expected.name} ${expected.description} [ Expected: "[Link should flag as we should be using a markdown link instead](#custom-section)" Actual: "Link should flag as we should be using a markdown link instead" ]`, + `${errorPath}:23 error ${expected.name} ${expected.description} [ Expected: "[Will flag](#custom-section)" Actual: "Will flag" ]`, + `${errorPath}:23 error ${expected.name} ${expected.description} [ Expected: "[multiple anchors](#assignment)" Actual: "multiple anchors" ]`, + `${errorPath}:26 error ${expected.name} ${expected.description} [ Expected: "[@TheOdinProjectExamples](https://codepen.io/TheOdinProjectExamples)" Actual: "@TheOdinProjectExamples" ]`, + `${errorPath}:34 error ${expected.name} ${expected.description} [ Expected: "[Flags knowledge check anchors](#knowledge-check)" Actual: "Flags knowledge check anchors" ]`, + ]); + }); + + it("Does not flag any errors if no rule violations", async () => { + const filePath = "./valid_anchors.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP007 in the fixed test md file", async () => { + const file = "./fixed_anchors_in_markdown.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP007 errors`, + ); + }); + + it("Converts flagged anchors to markdown links", async () => { + const fixedFileContents = await fixLintErrors("./anchors_in_markdown.md"); + const correctFile = await readFile( + join(__dirname, "./fixed_anchors_in_markdown.md"), + ); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP007_useMarkdownLinks/tests/anchors_in_markdown.md b/markdownlint/TOP007_useMarkdownLinks/tests/anchors_in_markdown.md new file mode 100644 index 00000000000..ec0551ff9ea --- /dev/null +++ b/markdownlint/TOP007_useMarkdownLinks/tests/anchors_in_markdown.md @@ -0,0 +1,40 @@ +### Introduction + +This file should flag with TOP007 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Assignment + +
+ +Assignment section + +
+ +#### Custom section + +Link should flag as we should be using a markdown link instead. + +Will flag if multiple anchors in same line. + + +@TheOdinProjectExamples + + + +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- Flags knowledge check anchors + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP007_useMarkdownLinks/tests/fixed_anchors_in_markdown.md b/markdownlint/TOP007_useMarkdownLinks/tests/fixed_anchors_in_markdown.md new file mode 100644 index 00000000000..1cd79963c9f --- /dev/null +++ b/markdownlint/TOP007_useMarkdownLinks/tests/fixed_anchors_in_markdown.md @@ -0,0 +1,40 @@ +### Introduction + +This file should flag with TOP007 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Assignment + +
+ +Assignment section + +
+ +#### Custom section + +[Link should flag as we should be using a markdown link instead](#custom-section). + +[Will flag](#custom-section) if [multiple anchors](#assignment) in same line. + + +[@TheOdinProjectExamples](https://codepen.io/TheOdinProjectExamples) + + + +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- [Flags knowledge check anchors](#knowledge-check) + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP007_useMarkdownLinks/tests/TOP007_test.md b/markdownlint/TOP007_useMarkdownLinks/tests/valid_anchors.md similarity index 67% rename from markdownlint/TOP007_useMarkdownLinks/tests/TOP007_test.md rename to markdownlint/TOP007_useMarkdownLinks/tests/valid_anchors.md index 47c225cac6c..9f45d6d0e30 100644 --- a/markdownlint/TOP007_useMarkdownLinks/tests/TOP007_test.md +++ b/markdownlint/TOP007_useMarkdownLinks/tests/valid_anchors.md @@ -1,6 +1,6 @@ ### Introduction -This file should flag with TOP007 errors, and no other linting errors. +This file should not flag any linting errors. ### Lesson overview @@ -18,18 +18,8 @@ Assignment section #### Custom section -```html -

- The following </p> should be ignored by this rule as it does not belong to a codepen embed. -

-``` - [Markdown links are desired in most cases](#custom-section) -Link should flag as we should be using a markdown link instead. - -Will flag if multiple anchors in same line. - `Anchors inside an inline code block are ignored` ```html @@ -45,16 +35,11 @@ on CodePen.

- -@TheOdinProjectExamples - - - ### Knowledge check The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. -- Flags with and omits non-href attributes +- [Does not flag markdown links](#href) ### Additional resources diff --git a/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/TOP008.test.js b/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/TOP008.test.js new file mode 100644 index 00000000000..4f4cfed114b --- /dev/null +++ b/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/TOP008.test.js @@ -0,0 +1,60 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP008_useBackticksForFencedCodeBlocks"); + +const pathInRepo = "markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests"; +const expected = { + name: "TOP008/use-backticks-for-fenced-code-blocks", + description: "Fenced code blocks should use backticks instead of tildes", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP008.md", + ), +}; + +describe("TOP008", () => { + describe("Lint", () => { + it("Links to the TOP008 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags code blocks with tilde delimiters", async () => { + const filePath = "./test.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:21 error ${expected.name} ${expected.description} [Expected: "\`\`\`"; Actual: "~~~"] [Context: "~~~text"]`, + `${errorPath}:23 error ${expected.name} ${expected.description} [Expected: "\`\`\`"; Actual: "~~~"] [Context: "~~~"]`, + `${errorPath}:25 error ${expected.name} ${expected.description} [Expected: "\`\`\`\`"; Actual: "~~~~"] [Context: "~~~~markdown"]`, + `${errorPath}:26 error ${expected.name} ${expected.description} [Expected: "\`\`\`"; Actual: "~~~"] [Context: "~~~text"]`, + `${errorPath}:28 error ${expected.name} ${expected.description} [Expected: "\`\`\`"; Actual: "~~~"] [Context: "~~~"]`, + `${errorPath}:29 error ${expected.name} ${expected.description} [Expected: "\`\`\`\`"; Actual: "~~~~"] [Context: "~~~~"]`, + `${errorPath}:33 error ${expected.name} ${expected.description} [Expected: "\`\`\`"; Actual: "~~~"] [Context: " ~~~text"]`, + `${errorPath}:35 error ${expected.name} ${expected.description} [Expected: "\`\`\`"; Actual: "~~~"] [Context: " ~~~"]`, + ]); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP008 in the fixed test md file", async () => { + const file = "./fixed_test.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP008 errors`, + ); + }); + + it("Replaces tilde delimiters with backticks", async () => { + const fixedFileContents = await fixLintErrors("./test.md"); + const correctFile = await readFile(join(__dirname, "./fixed_test.md")); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/fixed_test.md b/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/fixed_test.md new file mode 100644 index 00000000000..a5b889b8c7b --- /dev/null +++ b/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/fixed_test.md @@ -0,0 +1,57 @@ +### Introduction + +This file should flag with TOP008 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### Assignment + +
+ +Assignment section + +
+ +#### Custom section + +```text +This codeblock should flag an error as it uses tildes instead of backticks. +``` + +````markdown +```text +Parent and nested code blocks should both individually flag if tildes are used instead of backticks. +``` +```` + +1. List item + + ```text + Indented code blocks are treated all the same. + ``` + +```text +Backticks are valid and will not flag errors. +``` + +````markdown +```text +As will backticked parent and nested code blocks. +``` +```` + +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/TOP008_test.md b/markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/test.md similarity index 100% rename from markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/TOP008_test.md rename to markdownlint/TOP008_useBackticksForFencedCodeBlocks/tests/test.md diff --git a/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009.test.js b/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009.test.js new file mode 100644 index 00000000000..2dd1aa39a01 --- /dev/null +++ b/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009.test.js @@ -0,0 +1,50 @@ +const { join } = require("node:path"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const rule = require("../TOP009_lessonOverviewItemsSentenceStructure"); + +const pathInRepo = + "markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests"; +const expected = { + name: "TOP009/lesson-overview-items-sentence-structure", + description: + "Lesson overview items must be statements, not questions, and must begin with a capital letter and end with a period.", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP009.md", + ), +}; + +describe("TOP009", () => { + it("Links to the TOP009 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags when lesson overview item does not begin with a capital letter", async () => { + const filePath = "./capital_letter.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:10 error ${expected.name} ${expected.description} [Lesson overview items must be statements, not questions, and must begin with a capital letter and end with a period.] [Context: "lesson overview item 2."]`, + ]); + }); + + it("Flags when lesson overview item does not end with a period", async () => { + const filePath = "./invalid_punctuation.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:11 error ${expected.name} ${expected.description} [Lesson overview items must be statements, not questions, and must begin with a capital letter and end with a period.] [Context: "Lesson overview item 3?"]`, + `${errorPath}:13 error ${expected.name} ${expected.description} [Lesson overview items must be statements, not questions, and must begin with a capital letter and end with a period.] [Context: "Lesson overview item 6"]`, + ]); + }); + + it("Does not flag any errors if no rule violations", async () => { + const filePath = "./valid.md"; + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, []); + }); +}); diff --git a/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009_capital_letter.md b/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/capital_letter.md similarity index 100% rename from markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009_capital_letter.md rename to markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/capital_letter.md diff --git a/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009_invalid_punctuation.md b/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/invalid_punctuation.md similarity index 100% rename from markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009_invalid_punctuation.md rename to markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/invalid_punctuation.md diff --git a/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009_test_valid.md b/markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/valid.md similarity index 100% rename from markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/TOP009_test_valid.md rename to markdownlint/TOP009_lessonOverviewItemsSentenceStructure/tests/valid.md diff --git a/markdownlint/TOP010_useLazyNumbering/tests/TOP010.test.js b/markdownlint/TOP010_useLazyNumbering/tests/TOP010.test.js new file mode 100644 index 00000000000..e21861f66f2 --- /dev/null +++ b/markdownlint/TOP010_useLazyNumbering/tests/TOP010.test.js @@ -0,0 +1,56 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP010_useLazyNumbering"); + +const pathInRepo = "markdownlint/TOP010_useLazyNumbering/tests"; +const expected = { + name: "TOP010/lazy-numbering-for-ordered-lists", + description: "Ordered lists must always use 1. as a prefix (lazy numbering)", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP010.md", + ), +}; + +describe("TOP010", () => { + describe("Lint", () => { + it("Links to the TOP010 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags non-1 ordered list prefixes", async () => { + const filePath = "./test.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:26 error ${expected.name} ${expected.description} [ Expected: "1" Actual: "2" ]`, + `${errorPath}:28 error ${expected.name} ${expected.description} [ Expected: " 1" Actual: " 2" ]`, + `${errorPath}:29 error ${expected.name} ${expected.description} [ Expected: "1" Actual: "3" ]`, + `${errorPath}:38 error ${expected.name} ${expected.description} [ Expected: "1" Actual: "2" ]`, + ]); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP010 in the fixed test md file", async () => { + const file = "./fixed_test.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP010 errors`, + ); + }); + + it("Replaces non-1 ordered list prefixes with 1", async () => { + const fixedFileContents = await fixLintErrors("./test.md"); + const correctFile = await readFile(join(__dirname, "./fixed_test.md")); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP010_useLazyNumbering/tests/fixed_test.md b/markdownlint/TOP010_useLazyNumbering/tests/fixed_test.md new file mode 100644 index 00000000000..08856cbbd36 --- /dev/null +++ b/markdownlint/TOP010_useLazyNumbering/tests/fixed_test.md @@ -0,0 +1,55 @@ +### Introduction + +This file should flag with TOP010 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- A LESSON OVERVIEW ITEM. + +### CUSTOM SECTION HEADING + +CUSTOM SECTION CONTENT. + +### Assignment + +
+ +#### OPTIONAL CUSTOM ASSIGNMENT HEADING + +1. A RESOURCE OR EXERCISE ITEM + + - AN INSTRUCTION ITEM + +1. Item One +1. Item Two + 1. Child of Item Two + 1. Child of Item Two +1. Item Three + +1. Item One +1. Item Two + 1. Child of Item Two + 1. Child of Item Two +1. Item Three + +1. *foo* +1. *Bar* + +- This is an unordered list item to test TOP010 +- This is another unordered list item + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- [A KNOWLEDGE CHECK QUESTION](A-KNOWLEDGE-CHECK-URL) + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- It looks like this lesson doesn't have any additional resources yet. Help us expand this section by contributing to our curriculum. diff --git a/markdownlint/TOP010_useLazyNumbering/tests/TOP010_test.md b/markdownlint/TOP010_useLazyNumbering/tests/test.md similarity index 100% rename from markdownlint/TOP010_useLazyNumbering/tests/TOP010_test.md rename to markdownlint/TOP010_useLazyNumbering/tests/test.md diff --git a/markdownlint/TOP011_headingIndentation/TOP011_headingIndentation.js b/markdownlint/TOP011_headingIndentation/TOP011_headingIndentation.js index c10346e4ae0..e53d2709f6e 100644 --- a/markdownlint/TOP011_headingIndentation/TOP011_headingIndentation.js +++ b/markdownlint/TOP011_headingIndentation/TOP011_headingIndentation.js @@ -47,6 +47,7 @@ module.exports = { onError({ lineNumber: heading.lineNumber, detail: `Note box heading indented ${headingIndentation} spaces but should be indented ${noteBoxIndentation} spaces instead to match the containing note box.`, + context: heading.line, fixInfo: { lineNumber: heading.lineNumber, deleteCount: headingIndentation, @@ -62,6 +63,7 @@ module.exports = { onError({ lineNumber: heading.lineNumber, detail: `Normal headings must not be indented.`, + context: heading.line, fixInfo: { lineNumber: heading.lineNumber, deleteCount: headingIndentation, diff --git a/markdownlint/TOP011_headingIndentation/tests/TOP011.test.js b/markdownlint/TOP011_headingIndentation/tests/TOP011.test.js new file mode 100644 index 00000000000..4cd5ab6a374 --- /dev/null +++ b/markdownlint/TOP011_headingIndentation/tests/TOP011.test.js @@ -0,0 +1,55 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP011_headingIndentation"); + +const pathInRepo = "markdownlint/TOP011_headingIndentation/tests"; +const expected = { + name: "TOP011/heading-indentation", + description: "Headings must not be indented unless they are for a note box.", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP011.md", + ), +}; + +describe("TOP011", () => { + describe("Lint", () => { + it("Links to the TOP011 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags incorrectly indented headings", async () => { + const filePath = "./test.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:19 error ${expected.name} ${expected.description} [Normal headings must not be indented.] [Context: " ### This heading is indented so will flag an error"]`, + `${errorPath}:47 error ${expected.name} ${expected.description} [Note box heading indented 9 spaces but should be indented 7 spaces instead to match the containing note box.] [Context: " #### The note box and heading do not match indentation levels (7v9) so this should flag"]`, + `${errorPath}:53 error ${expected.name} ${expected.description} [Note box heading indented 2 spaces but should be indented 0 spaces instead to match the containing note box.] [Context: " #### The note box and heading do not match indentation levels (0v2) so this should flag"]`, + ]); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP011 in the fixed test md file", async () => { + const file = "./fixed_test.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP011 errors`, + ); + }); + + it("Fixes incorrect heading indentation", async () => { + const fixedFileContents = await fixLintErrors("./test.md"); + const correctFile = await readFile(join(__dirname, "./fixed_test.md")); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP011_headingIndentation/tests/fixed_test.md b/markdownlint/TOP011_headingIndentation/tests/fixed_test.md new file mode 100644 index 00000000000..fc7cb7b8155 --- /dev/null +++ b/markdownlint/TOP011_headingIndentation/tests/fixed_test.md @@ -0,0 +1,75 @@ +### Introduction + +Text content. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- LO item. + +### This heading is not indented so should NOT be flagged + +Some more content. + +#### Heading level makes no difference to indent expectations + +Some content. + +### This heading is indented so will flag an error + +
+ +#### The note box is not indented so this heading must also not be indented + +
+ +1. List item + 1. List item child. + +
+ + #### The note box and heading are both indented 3 spaces so should NOT be flagged + +
+ + - List item child UL. + - UL item child. + +
+ + #### The note box and heading are both indented 5 spaces so should NOT be flagged + +
+ +
+ + #### The note box and heading do not match indentation levels (7v9) so this should flag + +
+ +
+ +#### The note box and heading do not match indentation levels (0v2) so this should flag + +
+ +### Assignment + +
+ +Assignment content + +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- KC item + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- AR item diff --git a/markdownlint/TOP011_headingIndentation/tests/TOP011_test.md b/markdownlint/TOP011_headingIndentation/tests/test.md similarity index 100% rename from markdownlint/TOP011_headingIndentation/tests/TOP011_test.md rename to markdownlint/TOP011_headingIndentation/tests/test.md diff --git a/markdownlint/TOP012_noteBoxHeadings/TOP012_noteBoxHeadings.js b/markdownlint/TOP012_noteBoxHeadings/TOP012_noteBoxHeadings.js index 7b004316025..9227f07d1bc 100644 --- a/markdownlint/TOP012_noteBoxHeadings/TOP012_noteBoxHeadings.js +++ b/markdownlint/TOP012_noteBoxHeadings/TOP012_noteBoxHeadings.js @@ -54,6 +54,7 @@ module.exports = { onError({ lineNumber: heading.lineNumber, detail: `Expected a level 4 heading (####) but got a level ${heading.hashes.length} heading (${heading.hashes}) instead.`, + context: heading.text, fixInfo: { editColumn: hashesStartColumn, deleteCount: heading.hashes.length, diff --git a/markdownlint/TOP012_noteBoxHeadings/tests/TOP012.test.js b/markdownlint/TOP012_noteBoxHeadings/tests/TOP012.test.js new file mode 100644 index 00000000000..6b491d023ee --- /dev/null +++ b/markdownlint/TOP012_noteBoxHeadings/tests/TOP012.test.js @@ -0,0 +1,68 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const fixLintErrors = require("../../test_utils/fix")(__dirname); +const rule = require("../TOP012_noteBoxHeadings"); + +const pathInRepo = "markdownlint/TOP012_noteBoxHeadings/tests"; +const expected = { + name: "TOP012/note-box-headings", + description: "Note boxes have appropriate headings", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP012.md", + ), +}; + +describe("TOP012", () => { + describe("Lint", () => { + it("Links to the TOP012 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags note boxes with missing headings", async () => { + const filePath = "./missing_heading.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:13 error ${expected.name} ${expected.description} [Note box is missing a heading. Note boxes must start with a level 4 heading (####).]`, + ]); + }); + + it("Flags note boxes with incorrectly levelled headings", async () => { + const filePath = "./incorrect_heading_level.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:27 error ${expected.name} ${expected.description} [Expected a level 4 heading (####) but got a level 3 heading (###) instead.] [Context: "### Level 3 note box heading: Will flag error as it should be level 4"]`, + `${errorPath}:35 error ${expected.name} ${expected.description} [Expected a level 4 heading (####) but got a level 2 heading (##) instead.] [Context: "## Level 2 note box heading: Will flag error as it should be level 4"]`, + ]); + }); + }); + + describe("Fix", () => { + it("Does not flag TOP012 in the fixed test md file", async () => { + const file = "./fixed_incorrect_heading_level.md"; + const lintErrors = await getLintErrors(file); + + assert( + lintErrors.every((error) => !error.includes(expected.name)), + `"${file}" contains TOP012 errors`, + ); + }); + + it("Converts incorrectly levelled note box headings to level 4", async () => { + const fixedFileContents = await fixLintErrors( + "./incorrect_heading_level.md", + ); + const correctFile = await readFile( + join(__dirname, "./fixed_incorrect_heading_level.md"), + ); + + assert.equal(fixedFileContents, correctFile.toString()); + }); + }); +}); diff --git a/markdownlint/TOP012_noteBoxHeadings/tests/fixed_incorrect_heading_level.md b/markdownlint/TOP012_noteBoxHeadings/tests/fixed_incorrect_heading_level.md new file mode 100644 index 00000000000..16ed62aada0 --- /dev/null +++ b/markdownlint/TOP012_noteBoxHeadings/tests/fixed_incorrect_heading_level.md @@ -0,0 +1,57 @@ +### Introduction + +This file should flag with TOP012 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- A LESSON OVERVIEW ITEM. + +### Custom section + +#### Non-note box level 4 headings will not flag this error + +Custom subsection contents. + +
+ +#### Level 4 note box heading: Correct and will not flag error + +Note box contents. + +
+ +
+ +#### Level 3 note box heading: Will flag error as it should be level 4 + +Note box contents. + +
+ +
+ +#### Level 2 note box heading: Will flag error as it should be level 4 + +Note box contents. + +
+ +### Assignment + +
+ +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- [A KNOWLEDGE CHECK QUESTION](A-KNOWLEDGE-CHECK-URL) + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- It looks like this lesson doesn't have any additional resources yet. Help us expand this section by contributing to our curriculum. diff --git a/markdownlint/TOP012_noteBoxHeadings/tests/TOP012_test.md b/markdownlint/TOP012_noteBoxHeadings/tests/incorrect_heading_level.md similarity index 92% rename from markdownlint/TOP012_noteBoxHeadings/tests/TOP012_test.md rename to markdownlint/TOP012_noteBoxHeadings/tests/incorrect_heading_level.md index e4dbe2cd142..b80d66ab2ec 100644 --- a/markdownlint/TOP012_noteBoxHeadings/tests/TOP012_test.md +++ b/markdownlint/TOP012_noteBoxHeadings/tests/incorrect_heading_level.md @@ -38,12 +38,6 @@ Note box contents.
-
- -Note boxes without a heading will flag a missing heading error - -
- ### Assignment
diff --git a/markdownlint/TOP012_noteBoxHeadings/tests/missing_heading.md b/markdownlint/TOP012_noteBoxHeadings/tests/missing_heading.md new file mode 100644 index 00000000000..34ebeb57f01 --- /dev/null +++ b/markdownlint/TOP012_noteBoxHeadings/tests/missing_heading.md @@ -0,0 +1,35 @@ +### Introduction + +This file should flag with TOP012 errors, and no other linting errors. + +### Lesson overview + +This section contains a general overview of topics that you will learn in this lesson. + +- A LESSON OVERVIEW ITEM. + +### Custom section + +
+ +Note boxes without a heading will flag a missing heading error + +
+ +### Assignment + +
+ +
+ +### Knowledge check + +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. + +- [A KNOWLEDGE CHECK QUESTION](A-KNOWLEDGE-CHECK-URL) + +### Additional resources + +This section contains helpful links to related content. It isn't required, so consider it supplemental. + +- It looks like this lesson doesn't have any additional resources yet. Help us expand this section by contributing to our curriculum. diff --git a/markdownlint/TOP013_descriptiveHeadings/tests/TOP013.test.js b/markdownlint/TOP013_descriptiveHeadings/tests/TOP013.test.js new file mode 100644 index 00000000000..8774fe8ae96 --- /dev/null +++ b/markdownlint/TOP013_descriptiveHeadings/tests/TOP013.test.js @@ -0,0 +1,33 @@ +const { join } = require("node:path"); +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const getLintErrors = require("../../test_utils/lint")(__dirname); +const rule = require("../TOP013_descriptiveHeadings"); + +const pathInRepo = "markdownlint/TOP013_descriptiveHeadings/tests"; +const expected = { + name: "TOP013/descriptive-headings", + description: "Headings must have descriptive text", + information: new URL( + "https://github.com/TheOdinProject/curriculum/blob/main/markdownlint/docs/TOP013.md", + ), +}; + +describe("TOP013", () => { + it("Links to the TOP013 docs", () => { + assert.deepEqual(rule.information, expected.information); + }); + + it("Flags blacklisted headings", async () => { + const filePath = "./test.md"; + const errorPath = join(pathInRepo, filePath); + const lintErrors = await getLintErrors(filePath); + + assert.deepEqual(lintErrors, [ + `${errorPath}:15 error ${expected.name} ${expected.description} ["Important" is not sufficiently descriptive by itself. Use a more descriptive heading that briefly but clearly summarizes the content of the section.] [Context: "### Important"]`, + `${errorPath}:19 error ${expected.name} ${expected.description} ["A warning" is not sufficiently descriptive by itself. Use a more descriptive heading that briefly but clearly summarizes the content of the section.] [Context: "#### A warning"]`, + `${errorPath}:25 error ${expected.name} ${expected.description} ["Remember" is not sufficiently descriptive by itself. Use a more descriptive heading that briefly but clearly summarizes the content of the section.] [Context: "#### Remember!"]`, + `${errorPath}:35 error ${expected.name} ${expected.description} ["Important note" is not sufficiently descriptive by itself. Use a more descriptive heading that briefly but clearly summarizes the content of the section.] [Context: " #### Important note"]`, + ]); + }); +}); diff --git a/markdownlint/TOP013_descriptiveHeadings/tests/TOP013.md b/markdownlint/TOP013_descriptiveHeadings/tests/test.md similarity index 100% rename from markdownlint/TOP013_descriptiveHeadings/tests/TOP013.md rename to markdownlint/TOP013_descriptiveHeadings/tests/test.md diff --git a/markdownlint/docs/README.md b/markdownlint/docs/README.md index cdec4e49c1d..dd1e55a7a48 100644 --- a/markdownlint/docs/README.md +++ b/markdownlint/docs/README.md @@ -1,3 +1,77 @@ # TOP Custom Markdownlint Rules -This directory contains documentation for our custom rules for linting Markdown files using the markdownlint tool. These rules supplement the [default rules provided by markdownlint](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) to enforce our [curriculum's layout style](https://github.com/TheOdinProject/curriculum/blob/main/LAYOUT_STYLE_GUIDE.md). +This directory contains documentation for our custom rules for linting Markdown files using the [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) tool. These rules supplement the [default rules provided by markdownlint](https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md) to enforce our [curriculum's layout style](https://github.com/TheOdinProject/curriculum/blob/main/LAYOUT_STYLE_GUIDE.md). + +## Table of Contents + +- [How to Contribute](#how-to-contribute) +- [Custom Rules](#custom-rules) + - [File Structure](#file-structure) + - [Rule Code](#rule-code) + - [Tests](#tests) + - [Test Utilities](#test-utilities) + +## How to Contribute + +Make sure you have read our [general contributing guide](https://github.com/TheOdinProject/.github/blob/main/CONTRIBUTING.md), as it contains information that is important for all of our repos. + +This contributing guide assumes you have followed the instructions in the general contributing guide to fork and clone this curriculum repo. + +If you have a suggestion for a new linting rule, please **do not** open a pull request (PR) directly. Instead, open an issue with the full proposal so the team can discuss it first. + +## Custom Rules + +### File Structure + +Every custom rule should have its own directory inside `/markdownlint` (parent directory to this file), in the following format: + +```text +TOPXXX_ruleName/ +├── TOPXXX_ruleName.js +└── tests/ + ├── TOPXXX.test.js + └── ....md +``` + +It must also have a corresponding `TOPXXX.md` documentation file inside `/markdownlint/docs` (same directory as this file) that explains how the rule works, whether it includes auto-fix behaviour (and in what way), and the rationale behind the rule. + +A path to the `TOPXXX_ruleName.js` file must also be added to the `customRules` array in the `.markdownlint-cli2.jsonc` configuration file in the root of this repo. + +### Rule Code + +Refer to [markdonwlint custom rule documentation](https://github.com/DavidAnson/markdownlint/blob/main/doc/CustomRules.md) for general information about how to write a custom rule. + +Our custom rules must also ensure the following properties contain the following values: + +- `names`: An array with two elements: + - The rule code (e.g. `TOP001`) + - The rule name in kebab-case (e.g. `descriptive-link-text-labels`) +- `parser`: Must be set to `markdownit` +- `information`: A `new URL()` object that links to the rule's documentation file + +### Tests + +Tests are run via [Node's built-in test runner](https://nodejs.org/api/test.html). All rules require the following: + +- Any number of markdown files containing only violations of the respective rule. **These files must not include violations of other rules.** +- A markdown file containing no rule violations. +- A `.test.js` file containing tests that: + - Show the rule links to the correct documentation file + - Demonstrate the correct error output when linting markdown files with rule violations (will indirectly test for the correct rule name/description as they are included in the error output) + - Demonstrate the file with no rule violations has no error output + +For rules that contain auto-fix behavior, the following things are also required: + +- Any number of markdown files that contain the intended "fixed" contents of other test markdown files (the intended result of running `npm run fix` on them) +- Tests that: + - Show no error output from any "fixed" markdown files + - Show fixing a test markdown file results in the same contents as the respective "fixed" markdown file + +Tests can be run by running `npm run test`. + +### Test Utilities + +Two utility functions are provided in `/markdownlint/test_utils` for testing both linting and fixing: `getLintErrors` and `fixLintErrors`. For both of these, `require` them into the test file and call them with `__dirname` as an argument. These will each return an async function that's relative to the test file, so you only need to pass them paths relative to the test file. + +- `getLintErrors` takes a string containing the relative path to a test markdown file, and returns a Promise that resolves to an array containing each violation's error output as a separate string. +- `fixLintErrors` takes a string containing the relative path to a test markdown file, and returns a Promise that resolves to a string containing the contents of the file after fixing all fixable violations. diff --git a/markdownlint/test_utils/fix.js b/markdownlint/test_utils/fix.js new file mode 100644 index 00000000000..4384babcdf9 --- /dev/null +++ b/markdownlint/test_utils/fix.js @@ -0,0 +1,33 @@ +const { join } = require("node:path"); +const { readFile } = require("node:fs/promises"); +const { spawnSync } = require("node:child_process"); + +/** + * @param {string} dirname - Pass in the __dirname global for a function relative to the current test file + */ +module.exports = (dirname) => { + /** + * @param {string} filePath - Path to markdown file relative to current test file + * @returns {Promise} File contents after lint fixes applied + * @throws {Error} with .code === 'ENOENT' if no file at given path + */ + return async (filePath) => { + const markdownFileFullPath = join(dirname, filePath); + + // don't catch - we want this to throw and halt the tests if the file does not exist + const fileContents = await readFile(markdownFileFullPath); + const childProcess = spawnSync(`npm`, ["run", "lint", "--", "--format"], { + input: fileContents.toString(), + }); + + return ( + childProcess.stdout + .toString() + // get rid of the markdownlint-cli2 output noise + .replace( + "\n> curriculum@1.0.0 lint\n> markdownlint-cli2 --format\n\n", + "", + ) + ); + }; +}; diff --git a/markdownlint/test_utils/lint.js b/markdownlint/test_utils/lint.js new file mode 100644 index 00000000000..c7f35fa9a39 --- /dev/null +++ b/markdownlint/test_utils/lint.js @@ -0,0 +1,28 @@ +const { join } = require("node:path"); +const { access } = require("node:fs/promises"); +const { promisify } = require("node:util"); +const exec = promisify(require("node:child_process").exec); + +/** + * @param {string} dirname - Pass in the __dirname global for a function relative to the current test file + */ +module.exports = (dirname) => { + /** + * @param {string} filePath - Path to markdown file relative to current test file + * @returns {Promise} Array of markdownlint error strings, each error being its own element + * @throws {Error} with .code === 'ENOENT' if no file at given path + */ + return async (filePath) => { + const markdownFileFullPath = join(dirname, filePath); + + // don't catch - we want this to throw and halt the tests if the file does not exist + await access(markdownFileFullPath); + + try { + await exec(`npm run lint -- "${markdownFileFullPath}"`); + return []; + } catch (error) { + return error.stderr.trim().split("\n"); + } + }; +}; diff --git a/package-lock.json b/package-lock.json index 719714f3290..c401eee16d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "CC BY-NC-SA 4.0", "devDependencies": { - "markdownlint-cli2": "^0.12.1" + "markdownlint-cli2": "^0.20.0" } }, "node_modules/@nodelib/fs.scandir": { @@ -17,6 +17,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -30,6 +31,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -39,6 +41,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -48,10 +51,11 @@ } }, "node_modules/@sindresorhus/merge-streams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", - "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -59,11 +63,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/braces": { "version": "3.0.3", @@ -78,11 +127,111 @@ "node": ">=8" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -91,26 +240,28 @@ } }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" } }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -128,11 +279,25 @@ "node": ">=8" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -141,39 +306,79 @@ } }, "node_modules/globby": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", - "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-15.0.0.tgz", + "integrity": "sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==", "dev": true, + "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^1.0.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "path-type": "^6.0.0", "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -183,6 +388,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -190,6 +396,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -200,133 +417,735 @@ "node": ">=0.12.0" } }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/katex": { + "version": "0.16.27", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.27.tgz", + "integrity": "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, + "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" } }, "node_modules/markdown-it": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", - "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", - "uc.micro": "^2.0.0" + "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdownlint": { - "version": "0.33.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.33.0.tgz", - "integrity": "sha512-4lbtT14A3m0LPX1WS/3d1m7Blg+ZwiLq36WvjQqFGsX3Gik99NV+VXp/PW3n+Q62xyPdbvGOCfjPqjW+/SKMig==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz", + "integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==", "dev": true, + "license": "MIT", "dependencies": { - "markdown-it": "14.0.0", - "markdownlint-micromark": "0.1.8" + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2", + "string-width": "8.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/DavidAnson" } }, "node_modules/markdownlint-cli2": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.12.1.tgz", - "integrity": "sha512-RcK+l5FjJEyrU3REhrThiEUXNK89dLYNJCYbvOUKypxqIGfkcgpz8g08EKqhrmUbYfYoLC5nEYQy53NhJSEtfQ==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.20.0.tgz", + "integrity": "sha512-esPk+8Qvx/f0bzI7YelUeZp+jCtFOk3KjZ7s9iBQZ6HlymSXoTtWGiIRZP05/9Oy2ehIoIjenVwndxGtxOIJYQ==", "dev": true, + "license": "MIT", "dependencies": { - "globby": "14.0.0", - "jsonc-parser": "3.2.0", - "markdownlint": "0.33.0", - "markdownlint-cli2-formatter-default": "0.0.4", - "micromatch": "4.0.5", - "yaml": "2.3.4" + "globby": "15.0.0", + "js-yaml": "4.1.1", + "jsonc-parser": "3.3.1", + "markdown-it": "14.1.0", + "markdownlint": "0.40.0", + "markdownlint-cli2-formatter-default": "0.0.6", + "micromatch": "4.0.8" }, "bin": { - "markdownlint-cli2": "markdownlint-cli2.js" + "markdownlint-cli2": "markdownlint-cli2-bin.mjs" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/DavidAnson" } }, "node_modules/markdownlint-cli2-formatter-default": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.4.tgz", - "integrity": "sha512-xm2rM0E+sWgjpPn1EesPXx5hIyrN2ddUnUwnbCsD/ONxYtw3PX6LydvdH6dciWAoFDpwzbHM1TO7uHfcMd6IYg==", - "dev": true, - "peerDependencies": { - "markdownlint-cli2": ">=0.0.4" - } - }, - "node_modules/markdownlint-micromark": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.8.tgz", - "integrity": "sha512-1ouYkMRo9/6gou9gObuMDnvZM8jC/ly3QCFQyoSPCS2XV1ZClU0xpKbL1Ar3bWWRT1RnBZkWUEiNKrI2CwiBQA==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/markdownlint-cli2-formatter-default/-/markdownlint-cli2-formatter-default-0.0.6.tgz", + "integrity": "sha512-VVDGKsq9sgzu378swJ0fcHfSicUnMxnL8gnLm/Q4J/xsNJ4e5bA6lvAz7PCzIl0/No0lHyaWdqVD2jotxOSFMQ==", "dev": true, - "engines": { - "node": ">=16" - }, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/DavidAnson" + }, + "peerDependencies": { + "markdownlint-cli2": ">=0.0.4" } }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -337,6 +1156,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -349,6 +1169,7 @@ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -371,13 +1192,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -402,6 +1225,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -411,6 +1235,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -418,6 +1243,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -432,31 +1290,24 @@ } }, "node_modules/uc.micro": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", - "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" }, "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "dev": true, - "engines": { - "node": ">= 14" - } } } } diff --git a/package.json b/package.json index 9ec51c2af89..197407ef080 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,11 @@ "description": "[The Odin Project](https://www.theodinproject.com/) (TOP) is an open-source curriculum for learning full-stack web development. Our curriculum is divided into distinct courses, each covering the subject language in depth. Each course contains a listing of lessons interspersed with multiple projects. These projects give users the opportunity to practice what they are learning, thereby reinforcing and solidifying the theoretical knowledge learned in the lessons. Completed projects may then be included in the user's portfolio.", "scripts": { "lint": "markdownlint-cli2", - "fix": "markdownlint-cli2 --fix" + "fix": "markdownlint-cli2 --fix", + "test": "node --test" }, "license": "CC BY-NC-SA 4.0", "devDependencies": { - "markdownlint-cli2": "^0.12.1" + "markdownlint-cli2": "^0.20.0" } }