From e95b040762b59baccd20285312a3470d4a63745c Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Wed, 25 Sep 2024 19:04:16 +0200 Subject: [PATCH] refactor: create and use all-section parser (#56282) --- .../parser/plugins/add-assignment.js | 2 +- .../parser/plugins/add-fill-in-the-blank.js | 2 +- .../parser/plugins/add-quizzes.js | 27 ++---- .../parser/plugins/add-scene.js | 2 +- .../parser/plugins/add-seed.js | 2 +- .../parser/plugins/add-solution.js | 2 +- .../parser/plugins/add-tests.js | 2 +- .../parser/plugins/add-text.js | 2 +- .../parser/plugins/add-video-question.js | 2 +- .../parser/plugins/utils/find-all.js | 27 ++++++ .../parser/plugins/utils/find-all.test.js | 39 +++++++++ .../parser/plugins/utils/get-section.js | 83 +++++++++++-------- .../parser/plugins/utils/get-section.test.js | 2 +- 13 files changed, 131 insertions(+), 63 deletions(-) create mode 100644 tools/challenge-parser/parser/plugins/utils/find-all.js create mode 100644 tools/challenge-parser/parser/plugins/utils/find-all.test.js diff --git a/tools/challenge-parser/parser/plugins/add-assignment.js b/tools/challenge-parser/parser/plugins/add-assignment.js index b18a6195297..1c826fc7c09 100644 --- a/tools/challenge-parser/parser/plugins/add-assignment.js +++ b/tools/challenge-parser/parser/plugins/add-assignment.js @@ -1,4 +1,4 @@ -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const mdastToHtml = require('./utils/mdast-to-html'); const { splitOnThematicBreak } = require('./utils/split-on-thematic-break'); diff --git a/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js b/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js index a7a1fe52f26..5647c26a6b7 100644 --- a/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js +++ b/tools/challenge-parser/parser/plugins/add-fill-in-the-blank.js @@ -1,7 +1,7 @@ const { root } = require('mdast-builder'); const find = require('unist-util-find'); const visit = require('unist-util-visit'); -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const getAllBefore = require('./utils/before-heading'); const mdastToHtml = require('./utils/mdast-to-html'); diff --git a/tools/challenge-parser/parser/plugins/add-quizzes.js b/tools/challenge-parser/parser/plugins/add-quizzes.js index c1e3b9e788a..468b6af72ee 100644 --- a/tools/challenge-parser/parser/plugins/add-quizzes.js +++ b/tools/challenge-parser/parser/plugins/add-quizzes.js @@ -1,5 +1,5 @@ const { root } = require('mdast-builder'); -const getSection = require('./utils/get-section'); +const { getSection, getAllSections } = require('./utils/get-section'); const mdastToHtml = require('./utils/mdast-to-html'); const { splitOnThematicBreak } = require('./utils/split-on-thematic-break'); @@ -10,27 +10,16 @@ function plugin() { const quizzesNodes = getSection(tree, `--quizzes--`); if (quizzesNodes.length > 0) { - const quizzes = []; - const quizTrees = []; + const compiledQuizzes = []; + const quizSections = getAllSections(root(quizzesNodes), `--quiz--`); - quizzesNodes.forEach(quizNode => { - const isStartOfQuiz = quizNode.children?.[0]?.value === '--quiz--'; - if (isStartOfQuiz) { - quizTrees.push([quizNode]); - } else { - quizTrees[quizTrees.length - 1].push(quizNode); - } - }); - - if (quizTrees.length === 0) { + if (quizSections.length === 0) { throw Error( 'The --quizzes-- section should contain at least one quiz.' ); } - quizTrees.forEach(allQuizNodes => { - const quizTree = root(allQuizNodes); - const quizNodes = getSection(quizTree, `--quiz--`); + quizSections.forEach(quizNodes => { const quizQuestions = []; const questionTrees = []; @@ -62,11 +51,11 @@ function plugin() { ); }); - quizzes.push({ questions: quizQuestions }); + compiledQuizzes.push({ questions: quizQuestions }); }); - if (quizzes.length > 0) { - file.data.quizzes = quizzes; + if (compiledQuizzes.length > 0) { + file.data.quizzes = compiledQuizzes; } } } diff --git a/tools/challenge-parser/parser/plugins/add-scene.js b/tools/challenge-parser/parser/plugins/add-scene.js index b23ad4dd389..dc1251b0375 100644 --- a/tools/challenge-parser/parser/plugins/add-scene.js +++ b/tools/challenge-parser/parser/plugins/add-scene.js @@ -1,4 +1,4 @@ -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); function plugin() { return transformer; diff --git a/tools/challenge-parser/parser/plugins/add-seed.js b/tools/challenge-parser/parser/plugins/add-seed.js index c3a2a95fa74..0a7869c839e 100644 --- a/tools/challenge-parser/parser/plugins/add-seed.js +++ b/tools/challenge-parser/parser/plugins/add-seed.js @@ -1,7 +1,7 @@ const { isEmpty } = require('lodash'); const { root } = require('mdast-builder'); const visitChildren = require('unist-util-visit-children'); -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const { getFileVisitor } = require('./utils/get-file-visitor'); const editableRegionMarker = '--fcc-editable-region--'; diff --git a/tools/challenge-parser/parser/plugins/add-solution.js b/tools/challenge-parser/parser/plugins/add-solution.js index a7194b3f613..4d7e82ba0ce 100644 --- a/tools/challenge-parser/parser/plugins/add-solution.js +++ b/tools/challenge-parser/parser/plugins/add-solution.js @@ -3,7 +3,7 @@ const { root } = require('mdast-builder'); const visitChildren = require('unist-util-visit-children'); const { editableRegionMarker } = require('./add-seed'); -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const { getFileVisitor } = require('./utils/get-file-visitor'); const { splitOnThematicBreak } = require('./utils/split-on-thematic-break'); diff --git a/tools/challenge-parser/parser/plugins/add-tests.js b/tools/challenge-parser/parser/plugins/add-tests.js index 282db8f3c47..2776e2b0189 100644 --- a/tools/challenge-parser/parser/plugins/add-tests.js +++ b/tools/challenge-parser/parser/plugins/add-tests.js @@ -1,5 +1,5 @@ const chunk = require('lodash/chunk'); -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const mdastToHtml = require('./utils/mdast-to-html'); function plugin() { diff --git a/tools/challenge-parser/parser/plugins/add-text.js b/tools/challenge-parser/parser/plugins/add-text.js index f31d3590aab..07922b4dca6 100644 --- a/tools/challenge-parser/parser/plugins/add-text.js +++ b/tools/challenge-parser/parser/plugins/add-text.js @@ -1,5 +1,5 @@ const { isEmpty } = require('lodash'); -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const mdastToHTML = require('./utils/mdast-to-html'); function addText(sectionIds) { diff --git a/tools/challenge-parser/parser/plugins/add-video-question.js b/tools/challenge-parser/parser/plugins/add-video-question.js index f75decdc938..edf971bb544 100644 --- a/tools/challenge-parser/parser/plugins/add-video-question.js +++ b/tools/challenge-parser/parser/plugins/add-video-question.js @@ -1,6 +1,6 @@ const { root } = require('mdast-builder'); const find = require('unist-util-find'); -const getSection = require('./utils/get-section'); +const { getSection } = require('./utils/get-section'); const getAllBefore = require('./utils/before-heading'); const mdastToHtml = require('./utils/mdast-to-html'); diff --git a/tools/challenge-parser/parser/plugins/utils/find-all.js b/tools/challenge-parser/parser/plugins/utils/find-all.js new file mode 100644 index 00000000000..870055e8776 --- /dev/null +++ b/tools/challenge-parser/parser/plugins/utils/find-all.js @@ -0,0 +1,27 @@ +const visit = require('unist-util-visit'); +const _ = require('lodash'); + +/** + * Finds all nodes in a tree that match a given condition. This is a trivial + * extension of `unist-util-find` that returns all matching nodes. + * + * @param {Object} tree - The unist tree to search through. + * @param {Function|Object} condition - The condition to match nodes + * against. This can be a function that accepts a single node argument or an object to match. + * @returns {Array} An array of nodes that match the condition. + */ +function findAll(tree, condition) { + const predicate = _.iteratee(condition); + const results = []; + visit(tree, node => { + if (predicate(node)) { + results.push(node); + } + + return visit.CONTINUE; + }); + + return results; +} + +module.exports.findAll = findAll; diff --git a/tools/challenge-parser/parser/plugins/utils/find-all.test.js b/tools/challenge-parser/parser/plugins/utils/find-all.test.js new file mode 100644 index 00000000000..19b34fdd4a1 --- /dev/null +++ b/tools/challenge-parser/parser/plugins/utils/find-all.test.js @@ -0,0 +1,39 @@ +const { findAll } = require('./find-all'); + +const testTree = { + type: 'root', + children: [ + { + type: 'heading', + depth: 1, + children: [{ type: 'text', value: 'test', testId: 1 }] + }, + { + type: 'paragraph', + children: [{ type: 'text', value: 'different', testId: 2 }] + }, + { + type: 'heading', + depth: 2, + children: [{ type: 'text', value: 'test', testId: 3 }] + }, + { + type: 'heading', + depth: 1, + children: [{ type: 'text', value: 'test', testId: 4 }] + } + ] +}; + +describe('findAll', () => { + it('should return an array', () => { + expect(findAll(testTree, _node => false)).toEqual([]); + }); + it('should return an array of nodes that match the test', () => { + expect(findAll(testTree, { type: 'text', value: 'test' })).toEqual([ + { type: 'text', value: 'test', testId: 1 }, + { type: 'text', value: 'test', testId: 3 }, + { type: 'text', value: 'test', testId: 4 } + ]); + }); +}); diff --git a/tools/challenge-parser/parser/plugins/utils/get-section.js b/tools/challenge-parser/parser/plugins/utils/get-section.js index c93668cf7f8..faf0414b604 100644 --- a/tools/challenge-parser/parser/plugins/utils/get-section.js +++ b/tools/challenge-parser/parser/plugins/utils/get-section.js @@ -2,45 +2,58 @@ const find = require('unist-util-find'); const findAfter = require('unist-util-find-after'); const findAllAfter = require('unist-util-find-all-after'); const between = require('unist-util-find-all-between'); +const { findAll } = require('./find-all'); -function getSection(tree, marker) { - const start = find(tree, { - type: 'heading', - children: [ - { - type: 'text', - value: marker - } - ] - }); +function _getSection(tree) { + return start => { + if (!start) return []; - if (!start) return []; - - const isEnd = node => { - return ( - node.type === 'heading' && node.depth <= start.depth && isMarker(node) - ); - }; - - const isMarker = node => { - if (node.children && node.children[0]) { - const child = node.children[0]; + const isEnd = node => { return ( - child.type === 'text' && - child.value.startsWith('--') && - child.value.endsWith('--') + node.type === 'heading' && node.depth <= start.depth && isMarker(node) ); - } else { - return false; - } + }; + + const isMarker = node => { + if (node.children && node.children[0]) { + const child = node.children[0]; + return ( + child.type === 'text' && + child.value.startsWith('--') && + child.value.endsWith('--') + ); + } else { + return false; + } + }; + + const end = findAfter(tree, start, isEnd); + + const targetNodes = end + ? between(tree, start, end) + : findAllAfter(tree, start); + return targetNodes; }; - - const end = findAfter(tree, start, isEnd); - - const targetNodes = end - ? between(tree, start, end) - : findAllAfter(tree, start); - return targetNodes; } -module.exports = getSection; +const startNode = marker => ({ + type: 'heading', + children: [ + { + type: 'text', + value: marker + } + ] +}); + +function getSection(tree, marker) { + const start = find(tree, startNode(marker)); + return _getSection(tree)(start); +} + +function getAllSections(tree, marker) { + const starts = findAll(tree, startNode(marker)); + return starts.map(_getSection(tree)); +} + +module.exports = { getSection, getAllSections }; diff --git a/tools/challenge-parser/parser/plugins/utils/get-section.test.js b/tools/challenge-parser/parser/plugins/utils/get-section.test.js index 683d2a710f3..40984203ba8 100644 --- a/tools/challenge-parser/parser/plugins/utils/get-section.test.js +++ b/tools/challenge-parser/parser/plugins/utils/get-section.test.js @@ -3,7 +3,7 @@ const { root } = require('mdast-builder'); const find = require('unist-util-find'); const parseFixture = require('../../__fixtures__/parse-fixture'); -const getSection = require('./get-section'); +const { getSection } = require('./get-section'); describe('getSection', () => { let simpleAst, extraHeadingAst;