refactor: lint challenges as part of curriculum, not root (#65665)

Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
This commit is contained in:
Oliver Eyton-Williams
2026-02-04 11:42:21 +01:00
committed by GitHub
parent 5fa67063f5
commit 20e48dd846
30 changed files with 287 additions and 171 deletions
+10 -1
View File
@@ -1,8 +1,17 @@
/* eslint-disable filenames-simple/naming-convention */ /* eslint-disable filenames-simple/naming-convention */
import path from 'node:path';
import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged'; import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged';
const linterConfigPath = path.resolve(
import.meta.dirname,
'./challenges/.markdownlint.yaml'
);
export default { export default {
...createLintStagedConfig(import.meta.dirname), ...createLintStagedConfig(import.meta.dirname),
'./challenges/**/*.md': files => './challenges/**/*.md': files =>
files.map(filename => `node ../tools/scripts/lint/index.js '${filename}'`) files.map(
filename =>
`pnpm challenge-linter --config=${linterConfigPath} '${filename}'`
)
}; };
+2 -2
View File
@@ -41,7 +41,7 @@
"delete-step": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-step", "delete-step": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-step",
"delete-challenge": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-challenge", "delete-challenge": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-challenge",
"delete-task": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-task", "delete-task": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/delete-task",
"lint": "eslint --max-warnings 0", "lint": "eslint --max-warnings 0 && pnpm lint-challenges",
"lint-challenges": "tsx src/lint-localized", "lint-challenges": "tsx src/lint-localized",
"reorder-tasks": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/reorder-tasks", "reorder-tasks": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/reorder-tasks",
"update-challenge-order": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/update-challenge-order", "update-challenge-order": "tsx --tsconfig ../tools/challenge-helper-scripts/tsconfig.json ../tools/challenge-helper-scripts/update-challenge-order",
@@ -57,6 +57,7 @@
"@babel/register": "7.23.7", "@babel/register": "7.23.7",
"@freecodecamp/browser-scripts": "workspace:*", "@freecodecamp/browser-scripts": "workspace:*",
"@freecodecamp/challenge-builder": "workspace:*", "@freecodecamp/challenge-builder": "workspace:*",
"@freecodecamp/challenge-linter": "workspace:*",
"@freecodecamp/eslint-config": "workspace:*", "@freecodecamp/eslint-config": "workspace:*",
"@freecodecamp/shared": "workspace:*", "@freecodecamp/shared": "workspace:*",
"@total-typescript/ts-reset": "^0.6.1", "@total-typescript/ts-reset": "^0.6.1",
@@ -67,7 +68,6 @@
"@typescript/vfs-1.6.1": "npm:@typescript/vfs@1.6.1", "@typescript/vfs-1.6.1": "npm:@typescript/vfs@1.6.1",
"@vitest/ui": "^4.0.15", "@vitest/ui": "^4.0.15",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"glob": "8.1.0",
"joi": "17.12.2", "joi": "17.12.2",
"joi-objectid": "3.0.1", "joi-objectid": "3.0.1",
"js-yaml": "4.0.0", "js-yaml": "4.0.0",
-8
View File
@@ -1,8 +0,0 @@
var glob = require('glob');
const lint = require('../../tools/scripts/lint');
const { testedLang } = require('./config');
glob(`challenges/${testedLang()}/**/*.md`, (err, files) => {
if (!files.length) throw Error('No files found');
files.forEach(file => lint({ path: file }));
});
+8
View File
@@ -0,0 +1,8 @@
import path from 'node:path';
import { configure } from '@freecodecamp/challenge-linter';
import { testedLang } from './config';
const CONFIG_PATH = path.resolve(__dirname, '../challenges/.markdownlint.yaml');
const { lintAll } = configure(CONFIG_PATH);
lintAll(`challenges/${testedLang()}/**/*.md`);
+3 -4
View File
@@ -9,7 +9,7 @@ WORKDIR /home/node/build
COPY --chown=node:node *.* . COPY --chown=node:node *.* .
COPY --chown=node:node api/ api/ COPY --chown=node:node api/ api/
COPY --chown=node:node packages/shared/ packages/shared/ COPY --chown=node:node packages/ packages/
COPY --chown=node:node tools/ tools/ COPY --chown=node:node tools/ tools/
COPY --chown=node:node curriculum/ curriculum/ COPY --chown=node:node curriculum/ curriculum/
# TODO: AFAIK it's just the intro translations. Those should be folded into the # TODO: AFAIK it's just the intro translations. Those should be folded into the
@@ -36,7 +36,7 @@ WORKDIR /home/node/build
COPY --chown=node:node pnpm*.yaml . COPY --chown=node:node pnpm*.yaml .
COPY --chown=node:node package.json . COPY --chown=node:node package.json .
COPY --chown=node:node api/ api/ COPY --chown=node:node api/ api/
COPY --chown=node:node packages/shared/ packages/shared/ COPY --chown=node:node packages/ packages/
RUN npm i -g pnpm@10 RUN npm i -g pnpm@10
# Weirdly this config does not seem necessary for the new api (the same number # Weirdly this config does not seem necessary for the new api (the same number
@@ -52,10 +52,9 @@ WORKDIR /home/node/fcc
COPY --from=builder --chown=node:node /home/node/build/api/dist/ ./ COPY --from=builder --chown=node:node /home/node/build/api/dist/ ./
COPY --from=builder --chown=node:node /home/node/build/api/package.json api/ COPY --from=builder --chown=node:node /home/node/build/api/package.json api/
COPY --from=builder --chown=node:node /home/node/build/curriculum/generated/curriculum.json curriculum/generated/ COPY --from=builder --chown=node:node /home/node/build/curriculum/generated/curriculum.json curriculum/generated/
COPY --from=builder --chown=node:node /home/node/build/packages/shared/ packages/shared/ COPY --from=builder --chown=node:node /home/node/build/packages/ packages/
COPY --from=deps --chown=node:node /home/node/build/node_modules/ node_modules/ COPY --from=deps --chown=node:node /home/node/build/node_modules/ node_modules/
COPY --from=deps --chown=node:node /home/node/build/api/node_modules/ api/node_modules/ COPY --from=deps --chown=node:node /home/node/build/api/node_modules/ api/node_modules/
# packages/shared has no prod dependencies, so no need to copy them
CMD ["node", "api/src/server.js"] CMD ["node", "api/src/server.js"]
-3
View File
@@ -48,7 +48,6 @@
"knip:all": "npx -y knip@5 ", "knip:all": "npx -y knip@5 ",
"lint-root": "pnpm npm-run-all lint:*", "lint-root": "pnpm npm-run-all lint:*",
"lint": "turbo type-check && turbo lint && turbo lint-root", "lint": "turbo type-check && turbo lint && turbo lint-root",
"lint:challenges": "cd ./curriculum && pnpm run lint-challenges",
"lint:prettier": "prettier --list-different .", "lint:prettier": "prettier --list-different .",
"lint:css": "stylelint '**/*.css'", "lint:css": "stylelint '**/*.css'",
"preseed": "turbo setup", "preseed": "turbo setup",
@@ -92,10 +91,8 @@
"js-yaml": "3.14.1", "js-yaml": "3.14.1",
"lint-staged": "^16.2.7", "lint-staged": "^16.2.7",
"lodash": "4.17.21", "lodash": "4.17.21",
"markdownlint": "0.33.0",
"npm-run-all2": "5.0.2", "npm-run-all2": "5.0.2",
"prettier": "3.2.5", "prettier": "3.2.5",
"prismjs": "1.29.0",
"stylelint": "16.14.1", "stylelint": "16.14.1",
"tsx": "4.19.1", "tsx": "4.19.1",
"turbo": "^2.8.1", "turbo": "^2.8.1",
+1
View File
@@ -0,0 +1 @@
dist
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
import './dist/cli.js';
@@ -1,10 +1,17 @@
{ {
"name": "@freecodecamp/scripts-lint", "name": "@freecodecamp/challenge-linter",
"version": "0.0.1", "version": "0.0.1",
"description": "The freeCodeCamp.org open-source codebase and curriculum", "description": "The freeCodeCamp.org open-source codebase and curriculum",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"private": true, "private": true,
"main": "none", "main": "index.js",
"type": "module",
"bin": {
"challenge-linter": "./cli.js"
},
"exports": {
".": "./dist/index.js"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
@@ -15,15 +22,25 @@
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme", "homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
"author": "freeCodeCamp <team@freecodecamp.org>", "author": "freeCodeCamp <team@freecodecamp.org>",
"scripts": { "scripts": {
"build": "tsc",
"lint": "eslint --max-warnings 0", "lint": "eslint --max-warnings 0",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"test:ui": "vitest --ui" "test:ui": "vitest --ui",
"type-check": "tsc --noEmit"
}, },
"devDependencies": { "devDependencies": {
"@freecodecamp/eslint-config": "workspace:*", "@freecodecamp/eslint-config": "workspace:*",
"@types/glob": "^8.1.0",
"@types/js-yaml": "4.0.5",
"@types/yargs": "^17.0.35",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"vitest": "^3.2.4" "glob": "^8.1.0",
"markdownlint": "0.33.0",
"prismjs": "1.29.0",
"typescript": "5.9.3",
"vitest": "^3.2.4",
"yargs": "^17.7.2"
} }
} }
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { configure } from './index.js';
const argv = yargs(hideBin(process.argv))
.options({ config: { type: 'string' } })
.parseSync();
const configPath = argv.config;
const files = argv._;
if (!configPath) {
console.error(
'Error: Configuration path is required. Use --config <path-to-config>'
);
process.exit(1);
}
if (files.length === 0) {
console.error('Error: At least one file path is required to lint.');
process.exit(1);
}
const { lint } = configure(configPath);
files.forEach(filePath => {
lint({ path: filePath });
});
@@ -0,0 +1,13 @@
# This is a copy of those in the curriculum. They don't need to be in sync.
default: true # include all rules, with exceptions below
MD002: false # first heading should not be a top level heading
MD013: false # lines can be any length
MD022: false # headings don't need surrounding by newlines
MD024: false # no duplicate headers
MD025: false # headings are used as markers by the parser
MD031: true # fenced blocks do need surrounding by newlines
MD033: false # inline html is required
MD040: true # fenced code blocks should have a language specified
MD034: false # allow bare-URLs
MD036: false # TODO: **Example** is the main offender, should that be a heading?
whitespace: false # extra whitespace is ignored, so we don't enforce it.
+18
View File
@@ -0,0 +1,18 @@
import { readFileSync } from 'node:fs';
import YAML from 'js-yaml';
import glob from 'glob';
import { linter } from './linter/index.js';
export const configure = (configPath: string) => {
const lintRules = readFileSync(configPath, 'utf8');
const lint = linter(YAML.load(lintRules));
const lintAll = (pattern: string) => {
glob(pattern, (err, files) => {
if (!files.length) throw Error('No files found');
files.forEach(file => lint({ path: file }));
});
};
return { lint, lintAll };
};
@@ -1,11 +1,27 @@
import path from 'path'; import path from 'path';
import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest';
import lint from '.'; import {
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
vi
} from 'vitest';
import { configure } from './index.js';
describe('markdown linter', () => { describe('markdown linter', () => {
const good = { path: path.join(__dirname, './fixtures/good.md') }; const good = { path: path.join(__dirname, './fixtures/good.md') };
const badYML = { path: path.join(__dirname, './fixtures/badYML.md') }; const badYML = { path: path.join(__dirname, './fixtures/badYML.md') };
const badFencing = { path: path.join(__dirname, './fixtures/badFencing.md') }; const badFencing = { path: path.join(__dirname, './fixtures/badFencing.md') };
const configPath = path.join(__dirname, './fixtures/rules.yaml');
let lint;
beforeAll(() => {
lint = configure(configPath).lint;
});
beforeEach(() => { beforeEach(() => {
console.log = vi.fn(); console.log = vi.fn();
// the linter signals that a file failed by setting // the linter signals that a file failed by setting
@@ -0,0 +1,16 @@
export const names = ['closed-code-blocks'];
export const description = 'Code blocks must have closing triple backticks';
export const tags = ['code'];
function rule(params, onError) {
params.parsers.micromark.tokens
.filter(token => token.type === 'codeFenced')
.forEach(token => {
if (token.text.trim().slice(-3) !== '```') {
onError({
lineNumber: token.endLine,
detail: `Code blocks must have closing triple backticks.`
});
}
});
}
export { rule as function };
@@ -1,26 +1,29 @@
const markdownlint = require('markdownlint'); import markdownlint from 'markdownlint';
const lintPrism = require('./markdown-prism'); import * as lintPrism from './markdown-prism.js';
const lintYAML = require('./markdown-yaml'); import * as lintYAML from './markdown-yaml.js';
const fencedCodeBlock = require('./fenced-code-block'); import * as fencedCodeBlock from './fenced-code-block.js';
function linter(rules) { export function linter(rules) {
const lint = (file, next) => { const lint = (file, next) => {
const options = { const options = {
files: [file.path], files: [file.path],
config: rules, config: rules,
customRules: [lintYAML, lintPrism, fencedCodeBlock] customRules: [lintYAML, lintPrism, fencedCodeBlock]
}; };
markdownlint(options, function callback(err, result) { markdownlint(options, function callback(err, result) {
const resultString = (result || '').toString(); const resultString = (result || '').toString();
if (resultString) { if (resultString) {
process.exitCode = 1; process.exitCode = 1;
console.log(resultString); console.log(resultString);
} }
if (err) {
process.exitCode = 1;
console.error(err);
}
if (next) next(err, file); if (next) next(err, file);
}); });
}; };
return lint; return lint;
} }
module.exports = linter;
@@ -0,0 +1,45 @@
import components from 'prismjs/components.js';
export const names = ['prism-langs'];
export const description =
'Code block languages should be supported by PrismJS';
export const tags = ['prism'];
function rule(params, onError) {
params.tokens
.filter(param => param.type === 'fence')
.forEach(codeBlock => {
// whitespace around the language is ignored by the parser, as is case:
const baseLang = codeBlock.info.trim().toLowerCase();
const lang = getBaseLanguageName(baseLang);
// Rule MD040 checks if the block has a language, so this rule only
// comes into play if a language has been specified.
if (baseLang && !lang) {
onError({
lineNumber: codeBlock.lineNumber,
detail: `'${baseLang}' is not recognised.`
});
}
});
}
export { rule as function };
/*
* This is the method used by https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-prismjs/src/load-prism-language.js
*/
// Get the real name of a language given it or an alias
const getBaseLanguageName = nameOrAlias => {
if (components.languages[nameOrAlias]) {
return nameOrAlias;
}
return Object.keys(components.languages).find(language => {
const { alias } = components.languages[language];
if (!alias) return false;
if (Array.isArray(alias)) {
return alias.includes(nameOrAlias);
} else {
return alias === nameOrAlias;
}
});
};
@@ -0,0 +1,24 @@
import jsYaml from 'js-yaml';
export const names = ['yaml-linter'];
export const description = 'YAML code blocks should be valid';
export const tags = ['yaml'];
function rule(params, onError) {
params.tokens
.filter(param => param.type === 'fence')
.filter(param => param.info === 'yml' || param.info === 'yaml')
// TODO since the parser only looks for yml, should we reject yaml blocks?
.forEach(codeBlock => {
try {
jsYaml.safeLoad(codeBlock.content);
} catch (e) {
onError({
lineNumber: codeBlock.lineNumber,
detail: e.message,
context: codeBlock.line
});
}
});
}
export { rule as function };
+10
View File
@@ -0,0 +1,10 @@
{
"include": ["src"],
"extends": "../../tsconfig-base.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"outDir": "dist",
"noEmit": false
}
}
+56 -31
View File
@@ -62,18 +62,12 @@ importers:
lodash: lodash:
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
markdownlint:
specifier: 0.33.0
version: 0.33.0
npm-run-all2: npm-run-all2:
specifier: 5.0.2 specifier: 5.0.2
version: 5.0.2 version: 5.0.2
prettier: prettier:
specifier: 3.2.5 specifier: 3.2.5
version: 3.2.5 version: 3.2.5
prismjs:
specifier: 1.29.0
version: 1.29.0
stylelint: stylelint:
specifier: 16.14.1 specifier: 16.14.1
version: 16.14.1(typescript@5.9.3) version: 16.14.1(typescript@5.9.3)
@@ -719,6 +713,9 @@ importers:
'@freecodecamp/challenge-builder': '@freecodecamp/challenge-builder':
specifier: workspace:* specifier: workspace:*
version: link:../packages/challenge-builder version: link:../packages/challenge-builder
'@freecodecamp/challenge-linter':
specifier: workspace:*
version: link:../packages/challenge-linter
'@freecodecamp/eslint-config': '@freecodecamp/eslint-config':
specifier: workspace:* specifier: workspace:*
version: link:../packages/eslint-config version: link:../packages/eslint-config
@@ -749,9 +746,6 @@ importers:
eslint: eslint:
specifier: ^9.39.1 specifier: ^9.39.1
version: 9.39.2(jiti@2.6.1) version: 9.39.2(jiti@2.6.1)
glob:
specifier: 8.1.0
version: 8.1.0
joi: joi:
specifier: 17.12.2 specifier: 17.12.2
version: 17.12.2 version: 17.12.2
@@ -850,6 +844,45 @@ importers:
specifier: ^3.2.4 specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1) version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1)
packages/challenge-linter:
devDependencies:
'@freecodecamp/eslint-config':
specifier: workspace:*
version: link:../eslint-config
'@types/glob':
specifier: ^8.1.0
version: 8.1.0
'@types/js-yaml':
specifier: 4.0.5
version: 4.0.5
'@types/yargs':
specifier: ^17.0.35
version: 17.0.35
'@vitest/ui':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4)
eslint:
specifier: ^9.39.1
version: 9.39.2(jiti@2.6.1)
glob:
specifier: ^8.1.0
version: 8.1.0
markdownlint:
specifier: 0.33.0
version: 0.33.0
prismjs:
specifier: 1.29.0
version: 1.29.0
typescript:
specifier: 5.9.3
version: 5.9.3
vitest:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1)
yargs:
specifier: ^17.7.2
version: 17.7.2
packages/eslint-config: packages/eslint-config:
devDependencies: devDependencies:
'@babel/eslint-parser': '@babel/eslint-parser':
@@ -1188,21 +1221,6 @@ importers:
specifier: 5.9.3 specifier: 5.9.3
version: 5.9.3 version: 5.9.3
tools/scripts/lint:
devDependencies:
'@freecodecamp/eslint-config':
specifier: workspace:*
version: link:../../../packages/eslint-config
'@vitest/ui':
specifier: ^3.2.4
version: 3.2.4(vitest@3.2.4)
eslint:
specifier: ^9.39.1
version: 9.39.2(jiti@2.6.1)
vitest:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.9)(typescript@5.9.3))(terser@5.28.1)(tsx@4.21.0)(yaml@2.8.1)
tools/scripts/seed: tools/scripts/seed:
devDependencies: devDependencies:
'@freecodecamp/eslint-config': '@freecodecamp/eslint-config':
@@ -5491,6 +5509,9 @@ packages:
'@types/yargs@17.0.26': '@types/yargs@17.0.26':
resolution: {integrity: sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==} resolution: {integrity: sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==}
'@types/yargs@17.0.35':
resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
'@types/yauzl@2.10.3': '@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
@@ -13746,8 +13767,8 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uc.micro@2.0.0: uc.micro@2.1.0:
resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
umd@3.0.3: umd@3.0.3:
resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==}
@@ -20832,6 +20853,10 @@ snapshots:
dependencies: dependencies:
'@types/yargs-parser': 21.0.1 '@types/yargs-parser': 21.0.1
'@types/yargs@17.0.35':
dependencies:
'@types/yargs-parser': 21.0.1
'@types/yauzl@2.10.3': '@types/yauzl@2.10.3':
dependencies: dependencies:
'@types/node': 24.10.9 '@types/node': 24.10.9
@@ -26701,7 +26726,7 @@ snapshots:
linkify-it@5.0.0: linkify-it@5.0.0:
dependencies: dependencies:
uc.micro: 2.0.0 uc.micro: 2.1.0
lint-staged@16.2.7: lint-staged@16.2.7:
dependencies: dependencies:
@@ -26944,7 +26969,7 @@ snapshots:
linkify-it: 5.0.0 linkify-it: 5.0.0
mdurl: 2.0.0 mdurl: 2.0.0
punycode.js: 2.3.1 punycode.js: 2.3.1
uc.micro: 2.0.0 uc.micro: 2.1.0
markdown-table@2.0.0: markdown-table@2.0.0:
dependencies: dependencies:
@@ -30907,7 +30932,7 @@ snapshots:
terser@5.28.1: terser@5.28.1:
dependencies: dependencies:
'@jridgewell/source-map': 0.3.5 '@jridgewell/source-map': 0.3.5
acorn: 8.11.3 acorn: 8.15.0
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21
@@ -31162,7 +31187,7 @@ snapshots:
tsx@4.21.0: tsx@4.21.0:
dependencies: dependencies:
esbuild: 0.27.2 esbuild: 0.27.2
get-tsconfig: 4.10.1 get-tsconfig: 4.13.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
@@ -31294,7 +31319,7 @@ snapshots:
typescript@5.9.3: {} typescript@5.9.3: {}
uc.micro@2.0.0: {} uc.micro@2.1.0: {}
umd@3.0.3: {} umd@3.0.3: {}
-1
View File
@@ -9,7 +9,6 @@ packages:
- 'tools/client-plugins/*' - 'tools/client-plugins/*'
- 'tools/crowdin' - 'tools/crowdin'
- 'tools/daily-challenges' - 'tools/daily-challenges'
- 'tools/scripts/lint'
- 'tools/scripts/seed' - 'tools/scripts/seed'
- 'tools/scripts/seed-exams' - 'tools/scripts/seed-exams'
- 'packages/*' - 'packages/*'
-20
View File
@@ -1,20 +0,0 @@
const fs = require('fs');
const path = require('path');
const YAML = require('js-yaml');
const argv = require('yargs').argv;
const linter = require('./linter');
const CONFIG_PATH = path.resolve(
__dirname,
'../../../curriculum/challenges/.markdownlint.yaml'
);
const isMDRE = /.*\.md$/;
const lintRules = fs.readFileSync(CONFIG_PATH, 'utf8');
const lint = linter(YAML.load(lintRules));
const files = argv._.filter(arg => isMDRE.test(arg));
files.forEach(path => lint({ path: path }));
module.exports = lint;
@@ -1,17 +0,0 @@
module.exports = {
names: ['closed-code-blocks'],
description: 'Code blocks must have closing triple backticks',
tags: ['code'],
function: function rule(params, onError) {
params.parsers.micromark.tokens
.filter(token => token.type === 'codeFenced')
.forEach(token => {
if (token.text.trim().slice(-3) !== '```') {
onError({
lineNumber: token.endLine,
detail: `Code blocks must have closing triple backticks.`
});
}
});
}
};
@@ -1,44 +0,0 @@
const components = require(`prismjs/components`);
module.exports = {
names: ['prism-langs'],
description: 'Code block languages should be supported by PrismJS',
tags: ['prism'],
function: function rule(params, onError) {
params.tokens
.filter(param => param.type === 'fence')
.forEach(codeBlock => {
// whitespace around the language is ignored by the parser, as is case:
const baseLang = codeBlock.info.trim().toLowerCase();
const lang = getBaseLanguageName(baseLang);
// Rule MD040 checks if the block has a language, so this rule only
// comes into play if a language has been specified.
if (baseLang && !lang) {
onError({
lineNumber: codeBlock.lineNumber,
detail: `'${baseLang}' is not recognised.`
});
}
});
}
};
/*
* This is the method used by https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-prismjs/src/load-prism-language.js
*/
// Get the real name of a language given it or an alias
const getBaseLanguageName = nameOrAlias => {
if (components.languages[nameOrAlias]) {
return nameOrAlias;
}
return Object.keys(components.languages).find(language => {
const { alias } = components.languages[language];
if (!alias) return false;
if (Array.isArray(alias)) {
return alias.includes(nameOrAlias);
} else {
return alias === nameOrAlias;
}
});
};
@@ -1,24 +0,0 @@
const yaml = require('js-yaml');
module.exports = {
names: ['yaml-linter'],
description: 'YAML code blocks should be valid',
tags: ['yaml'],
function: function rule(params, onError) {
params.tokens
.filter(param => param.type === 'fence')
.filter(param => param.info === 'yml' || param.info === 'yaml')
// TODO since the parser only looks for yml, should we reject yaml blocks?
.forEach(codeBlock => {
try {
yaml.safeLoad(codeBlock.content);
} catch (e) {
onError({
lineNumber: codeBlock.lineNumber,
detail: e.message,
context: codeBlock.line
});
}
});
}
};
-3
View File
@@ -1,3 +0,0 @@
{
"extends": "../../../tsconfig-base.json"
}