From 16ce662ebcfa5067515618a19b9d3415b59d3622 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Mon, 17 Mar 2025 21:15:02 -0400 Subject: [PATCH 1/5] build: add remark plugin for validating HTML section structure --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: passed - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: passed - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- .../README.md | 109 +++++++ .../examples/index.js | 59 ++++ .../lib/index.js | 50 +++ .../lib/main.js | 300 ++++++++++++++++++ .../package.json | 52 +++ .../test/fixtures/mismatched_class.md.txt | 13 + .../test/fixtures/missing_empty_line.md.txt | 11 + .../fixtures/missing_section_comment.md.txt | 11 + .../test/fixtures/orphaned_section_end.md.txt | 7 + .../test/fixtures/unclosed_section.md.txt | 11 + .../test/fixtures/valid.md.txt | 13 + .../test/fixtures/valid_multiple.md.txt | 37 +++ .../test/fixtures/valid_nested.md.txt | 93 ++++++ .../test/fixtures/valid_no_sections.md.txt | 10 + .../test/test.js | 217 +++++++++++++ 15 files changed, 993 insertions(+) create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/examples/index.js create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/index.js create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/package.json create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/mismatched_class.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_empty_line.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_section_comment.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/orphaned_section_end.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/unclosed_section.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_multiple.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_nested.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_no_sections.md.txt create mode 100644 lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/test.js diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md new file mode 100644 index 000000000000..5ad8c7c5ad05 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md @@ -0,0 +1,109 @@ + + +# remark-lint-html-section-structure + +> [remark][remark] plugin to lint HTML section structure in README files. + +
+ +This plugin validates the HTML section structure in README files, ensuring that: + +1. Each opening `
` tag has a corresponding `
` closing tag. +2. Each opening `
` tag has a corresponding closing comment ``. +3. There is an empty line between the closing `
` tag and the closing comment ``. + +
+ + + +
+ +## Usage + +```javascript +var sectionStructure = require( '@stdlib/_tools/remark/plugins/remark-lint-html-section-structure' ); +``` + +### sectionStructure() + +Plugin to lint HTML section structure in README files. + +```javascript +var remark = require( 'remark' ); + +remark().use( sectionStructure ); +``` + +
+ + + +
+ +## Examples + +```javascript +var remark = require( 'remark' ); +var report = require( 'vfile-reporter' ); +var sectionStructure = require( '@stdlib/_tools/remark/plugins/remark-lint-html-section-structure' ); + +// Create a markdown file with HTML sections: +var markdown = [ + '# Title', + '', + '
', + '', + '## Usage', + '', + '```javascript', + 'var foo = require( \'foo\' );', + '```', + '', + '
', + '', + '', + '' +].join( '\n' ); + +// Lint using the plugin: +remark() + .use( sectionStructure ) + .process( markdown, onDone ); + +function onDone( error, file ) { + if ( error ) { + throw error; + } + console.log( report( file ) ); +} +``` + +
+ + + + + + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/examples/index.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/examples/index.js new file mode 100644 index 000000000000..9c7ebb50b242 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/examples/index.js @@ -0,0 +1,59 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +var remark = require( 'remark' ); +var report = require( 'vfile-reporter' ); +var linter = require( './../lib' ); + +// Create a markdown file with both valid and invalid HTML sections: +var markdown = [ + '# Title', + '', + '
', + '', + '## Usage', + '', + '```javascript', + 'var foo = require( \'foo\' );', + '```', + '', + '
', + '', + '' +].join( '\n' ); + +// Lint using the plugin: +remark() + .use( linter ) + .process( markdown, done ); + +/** +* Callback invoked upon processing a Markdown file. +* +* @private +* @param {Error|null} error - error object +* @param {VFile} file - virtual file +*/ +function done( error, file ) { + if ( error ) { + throw error; + } + console.log( report( file ) ); +} diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/index.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/index.js new file mode 100644 index 000000000000..493beec0be5c --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/index.js @@ -0,0 +1,50 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +/** +* remark lint plugin for validating HTML section structure in README files. +* +* @module @stdlib/_tools/remark/plugins/remark-lint-html-section-structure +* +* @example +* var remark = require( 'remark' ); +* var lint = require( '@stdlib/_tools/remark/plugins/remark-lint-html-section-structure' ); +* +* var str = '# Title\n\n
\n\n## Usage\n\n
'; +* +* function done( error, file ) { +* var i; +* if ( error ) { +* throw error; +* } +* for ( i = 0; i < file.messages.length; i++ ) { +* console.error( file.messages[ i ].message ); +* } +* } +*/ + +// MODULES // + +var main = require( './main.js' ); + + +// EXPORTS // + +module.exports = main; diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js new file mode 100644 index 000000000000..3f87b5f67631 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js @@ -0,0 +1,300 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2022 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var logger = require( 'debug' ); +var rule = require( 'unified-lint-rule' ); +var visit = require( 'unist-util-visit' ); + + +// VARIABLES // + +var debug = logger( 'remark-lint-html-section-structure' ); +var SECTION_START = //; +var SECTION_END = /<\/section>$/; +var SECTION_END_WITH_COMMENT = /<\/section>\s*/; +var END_COMMENT = /^\s*/; +var END_COMMENT_SIMPLE = //; +var htmlSectionStructureRule; + + +// MAIN // + +/** +* Validates HTML section structure in README files. +* +* @private +* @param {Node} tree - abstract syntax tree (AST) +* @param {File} file - virtual file +* @param {Object} options - options +* @param {Callback} clbk - callback to invoke upon completion +* @returns {void} +*/ +function linter( tree, file, options, clbk ) { + var isValidWhitespace; + var combinedMatch; + var rootSections; + var sectionStack; + var commentClass; + var sectionMatch; + var simpleMatch; + var commentNode; + var commentLine; + var structure; + var positions; + var className; + var nextNode; + var endMatch; + var endLine; + var lineGap; + var current; + var match; + var value; + var data; + var msg; + var pos; + var i; + + debug( 'Linting file: %s', file.path || '' ); + + sectionStack = []; + rootSections = []; + structure = []; + positions = {}; + + visit( tree, 'html', visitor ); + + debug( 'Final section stack length: %d', sectionStack.length ); + + if ( sectionStack.length > 0 ) { + debug( 'Found unclosed sections: %d', sectionStack.length ); + for ( i = 0; i < sectionStack.length; i++ ) { + className = sectionStack[ i ].className || '[no class specified]'; + pos = sectionStack[ i ].position.start.line + ':' + sectionStack[ i ].position.start.column; + msg = pos + ' error Unclosed section tag. Missing closing tag for section with class="' + className + '". Add a closing tag to fix. unclosed-section'; + debug( msg ); + file.message( msg, sectionStack[ i ] ); + } + } + + debug( 'Finished linting: %s', file.path || '' ); + return clbk(); + + /** + * Callback invoked upon finding a matching node. + * + * @private + * @param {Object} node - AST node + * @param {number} index - position of `node` in `parent` + * @param {Object} parent - parent AST node + * @returns {void} + */ + function visitor( node, index, parent ) { + // Store position for tracking purposes + if ( node.position ) { + positions[ node.position.start.line ] = node; + } + + // Check if this is a combined section end with comment on the same line (which is an error) + combinedMatch = SECTION_END_WITH_COMMENT.exec( node.value ); + if ( combinedMatch ) { + debug( 'Found combined section end with comment: %s', node.value ); + pos = node.position.start.line + ':' + node.position.start.column; + msg = pos + ' error Missing empty line between closing section tag and comment. There should be an empty line between and missing-empty-line'; + debug( msg ); + file.message( msg, node ); + + // Make sure to pop the section stack for this case - otherwise we'll get "unclosed section" errors + if ( sectionStack.length > 0 ) { + debug( 'Popping section stack for combined section end+comment' ); + sectionStack.pop(); + } + return; + } + + // Check if this is a section start tag + sectionMatch = SECTION_START.exec( node.value ); + if ( sectionMatch ) { + debug( 'Found a section start tag with class: %s', sectionMatch[1] || '[none]' ); + + data = { + 'node': node, + 'className': sectionMatch[1] || '', + 'position': node.position, + 'children': [], + 'index': index, + 'parent': parent + }; + + if ( sectionStack.length === 0 ) { + // This is a root level section + rootSections.push( data ); + structure.push( data ); + } else { + // This is a nested section, add to parent's children + sectionStack[sectionStack.length - 1].children.push( data ); + } + + sectionStack.push( data ); + return; + } + + // Check if this is a section end tag + endMatch = SECTION_END.exec( node.value ); + if ( endMatch ) { + debug( 'Found a section end tag' ); + + if ( sectionStack.length === 0 ) { + debug( 'Orphaned section end tag found' ); + pos = node.position.start.line + ':' + node.position.start.column; + msg = pos + ' error Orphaned section closing tag. Found without a matching opening
tag. Remove this tag or add a corresponding opening tag. orphaned-section-end'; + debug( msg ); + file.message( msg, node ); + return; + } + + // Special handling for direct closing tag followed by a comment (no text node between) + if ( index + 1 < parent.children.length ) { + nextNode = parent.children[index + 1]; + + // Look at the next node to check if it's a comment + if ( nextNode.type === 'html' ) { + // Check if the next node is a comment but NOT on the same line + // This avoids double-reporting with the SECTION_END_WITH_COMMENT check above + match = END_COMMENT.exec( nextNode.value ); + simpleMatch = END_COMMENT_SIMPLE.exec( nextNode.value ); + + // If either regex matches, we've found a comment + if ( match || simpleMatch ) { + // Check if there are at least 2 line breaks between them + endLine = node.position.end.line; + commentLine = nextNode.position.start.line; + lineGap = commentLine - endLine; + + debug( 'Gap between section end and comment: %d lines', lineGap ); + + // We require at least 2 lines of gap for an empty line + if ( lineGap < 2 ) { + pos = node.position.start.line + ':' + node.position.start.column; + msg = pos + ' error Missing proper empty line after closing section tag. There should be an empty line between
and the closing comment. missing-empty-line-after-section'; + debug( msg ); + file.message( msg, node ); + } + + // Verify the comment matches the class name: + current = sectionStack[sectionStack.length - 1]; + + // Get class from whichever regex matched: + if ( match ) { + commentClass = match[1]; + } else { + commentClass = simpleMatch[1].trim(); + } + + // Verify the comment matches the class name: + if ( current.className && commentClass !== current.className ) { + debug( 'Mismatched section class in comment: expected %s, got %s', current.className, commentClass ); + pos = nextNode.position.start.line + ':' + nextNode.position.start.column; + msg = pos + ' error Mismatched section class in closing comment. Opening tag has class="' + current.className + '" but closing comment is . They should match. mismatched-section-class'; + debug( msg ); + file.message( msg, nextNode ); + } + + // Pop the section stack before returning + sectionStack.pop(); + return; + } + } + } + + // Standard handling for other cases with whitespace between section end and comment... + if ( index + 2 < parent.children.length ) { + nextNode = parent.children[index + 1]; + commentNode = parent.children[index + 2]; + + // Check if the next node is a proper whitespace with empty line: + isValidWhitespace = false; + if (nextNode.type === 'text') { + value = nextNode.value; + + // Look for at least two consecutive newlines (representing an empty line): + isValidWhitespace = ((/(\r?\n)\s*(\r?\n)/).test(value)); + } + + if ( !isValidWhitespace ) { + debug( 'Invalid whitespace after section end' ); + pos = node.position.start.line + ':' + node.position.start.column; + msg = pos + ' error Missing proper empty line after closing section tag. There should be an empty line between and the closing comment. missing-empty-line-after-section'; + debug( msg ); + file.message( msg, node ); + } + + // Check if the following node is the expected comment + if ( commentNode.type === 'html' ) { + match = END_COMMENT.exec( commentNode.value ); + if ( match ) { + current = sectionStack[sectionStack.length - 1]; + + // Verify the comment matches the class name + if ( current.className && match[1] !== current.className ) { + debug( 'Mismatched section class in comment: expected %s, got %s', current.className, match[1] ); + pos = commentNode.position.start.line + ':' + commentNode.position.start.column; + msg = pos + ' error Mismatched section class in closing comment. Opening tag has class="' + current.className + '" but closing comment is . They should match. mismatched-section-class'; + debug( msg ); + file.message( msg, commentNode ); + } + } else { + // The HTML node exists but it's not a proper closing comment + debug( 'Invalid closing comment format' ); + pos = commentNode.position.start.line + ':' + commentNode.position.start.column; + msg = pos + ' error Invalid section closing comment format. Expected invalid-closing-comment'; + debug( msg ); + file.message( msg, commentNode ); + } + } else { + // The node after whitespace is not an HTML node (comment) + debug( 'Node after whitespace is not an HTML comment' ); + pos = node.position.start.line + ':' + node.position.start.column; + msg = pos + ' error Missing section closing comment. Section closing tag should be followed by comment. missing-section-comment'; + debug( msg ); + file.message( msg, node ); + } + } else { + // There's not enough nodes after the section closing tag + debug( 'Not enough nodes after section end' ); + pos = node.position.start.line + ':' + node.position.start.column; + msg = pos + ' error Missing section closing comment. Section closing tag should be followed by comment on a separate line. missing-section-comment'; + debug( msg ); + file.message( msg, node ); + } + + // Pop the section stack + sectionStack.pop(); + } + } +} + +htmlSectionStructureRule = rule( 'remark-lint:html-section-structure', linter ); + + +// EXPORTS // + +module.exports = htmlSectionStructureRule; diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/package.json b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/package.json new file mode 100644 index 000000000000..ce16e5213092 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/package.json @@ -0,0 +1,52 @@ +{ + "name": "@stdlib/_tools/remark/plugins/remark-lint-html-section-structure", + "version": "0.0.0", + "description": "remark lint plugin for validating HTML section structure in README files.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "main": "./lib", + "directories": { + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "scripts": {}, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/stdlib.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": {}, + "devDependencies": {}, + "engines": { + "node": ">=6.0.0", + "npm": ">2.7.0" + }, + "keywords": [ + "stdlib", + "tools", + "lint", + "linter", + "markdown", + "md", + "remark", + "remark-plugin", + "plugin", + "html", + "section", + "structure", + "validate" + ] +} diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/mismatched_class.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/mismatched_class.md.txt new file mode 100644 index 000000000000..0969fd2f3162 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/mismatched_class.md.txt @@ -0,0 +1,13 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + +
+ + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_empty_line.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_empty_line.md.txt new file mode 100644 index 000000000000..7d0ae4fdda0a --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_empty_line.md.txt @@ -0,0 +1,11 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + +
diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_section_comment.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_section_comment.md.txt new file mode 100644 index 000000000000..e11fe286f97a --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/missing_section_comment.md.txt @@ -0,0 +1,11 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + +
diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/orphaned_section_end.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/orphaned_section_end.md.txt new file mode 100644 index 000000000000..dd57b6e2c341 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/orphaned_section_end.md.txt @@ -0,0 +1,7 @@ +# Example + +Some content... + + + + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/unclosed_section.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/unclosed_section.md.txt new file mode 100644 index 000000000000..ba96db797a11 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/unclosed_section.md.txt @@ -0,0 +1,11 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid.md.txt new file mode 100644 index 000000000000..8b913517f299 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid.md.txt @@ -0,0 +1,13 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + +
+ + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_multiple.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_multiple.md.txt new file mode 100644 index 000000000000..50197c4418de --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_multiple.md.txt @@ -0,0 +1,37 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + +
+ + + +
+ +## Examples + +```javascript +var foo = require( 'foo' ); + +// Usage example +foo(); +``` + +
+ + + + + + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_nested.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_nested.md.txt new file mode 100644 index 000000000000..693d42afe65c --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_nested.md.txt @@ -0,0 +1,93 @@ +# Example + +
+ +## Usage + +```javascript +var foo = require( 'foo' ); +``` + +
+ + + +
+ +## Examples + +```javascript +var foo = require( 'foo' ); +// Example +``` + +
+ + + +
+ +## C APIs + +
+ +### Introduction + +This section contains C API documentation. + +
+ + + +
+ +### Usage + +```c +#include "foo.h" +``` + +
+ + + +
+ +### Notes + +Some implementation notes... + +
+ + + +
+ +### Examples + +```c +// C example +#include "foo.h" + +int main() { + foo(); + return 0; +} +``` + +
+ + + +
+ + + + + + diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_no_sections.md.txt b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_no_sections.md.txt new file mode 100644 index 000000000000..ec6828393c75 --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/fixtures/valid_no_sections.md.txt @@ -0,0 +1,10 @@ +# Title + +## Heading 2 + +This is a regular markdown file with no HTML sections. + +* List item 1 +* List item 2 + +Some more text. diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/test.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/test.js new file mode 100644 index 000000000000..6f0e4ad2889d --- /dev/null +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/test/test.js @@ -0,0 +1,217 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2025 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var join = require( 'path' ).join; +var tape = require( 'tape' ); +var remark = require( 'remark' ); +var readSync = require( 'to-vfile' ).readSync; // eslint-disable-line node/no-sync +var isArray = require( '@stdlib/assert/is-array' ); +var IS_BROWSER = require( '@stdlib/assert/is-browser' ); +var lint = require( './../lib' ); + + +// VARIABLES // + +var opts = { + 'skip': IS_BROWSER +}; + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof lint, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the plugin successfully lints valid Markdown not containing any HTML sections', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'valid_no_sections.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 0, 'is empty array' ); + } + t.end(); + } +}); + +tape( 'the plugin successfully lints valid Markdown not containing any HTML sections (string)', opts, function test( t ) { + remark().use( lint ).process( '# Beep\n\n## Boop\n', done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 0, 'is empty array' ); + } + t.end(); + } +}); + +tape( 'the plugin successfully lints a valid HTML section structure', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'valid.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 0, 'is empty array' ); + } + t.end(); + } +}); + +tape( 'the plugin successfully lints valid HTML section structure (multiple sections)', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'valid_multiple.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 0, 'is empty array' ); + } + t.end(); + } +}); + +tape( 'the plugin successfully lints valid HTML section structure (nested sections)', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'valid_nested.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 0, 'is empty array' ); + } + t.end(); + } +}); + +tape( 'the plugin returns a lint error if a section is missing a closing tag', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'unclosed_section.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 1, 'contains lint error' ); + t.pass( file.messages[ 0 ] ); + } + t.end(); + } +}); + +tape( 'the plugin returns a lint error if a section closing tag is missing a comment', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'missing_section_comment.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 1, 'contains lint error' ); + t.pass( file.messages[ 0 ] ); + } + t.end(); + } +}); + +tape( 'the plugin returns a lint error if a section closing comment does not match the class', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'mismatched_class.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 1, 'contains lint error' ); + t.pass( file.messages[ 0 ] ); + } + t.end(); + } +}); + +tape( 'the plugin returns a lint error if a section end tag has no matching opening tag', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'orphaned_section_end.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 1, 'contains lint error' ); + t.pass( file.messages[ 0 ] ); + } + t.end(); + } +}); + +tape( 'the plugin returns a lint error if a section is missing an empty line between closing tag and comment', opts, function test( t ) { + var fpath = join( __dirname, 'fixtures', 'missing_empty_line.md.txt' ); + var file = readSync( fpath, 'utf8' ); + + remark().use( lint ).process( file, done ); + + function done( error, file ) { + if ( error ) { + t.fail( error.message ); + } else { + t.strictEqual( isArray( file.messages ), true, 'is an array' ); + t.strictEqual( file.messages.length, 1, 'contains lint error' ); + t.pass( file.messages[ 0 ] ); + } + t.end(); + } +}); From e32f82a51ac4ac15a636228bafe7cbf4fcb34fef Mon Sep 17 00:00:00 2001 From: stdlib-bot <82920195+stdlib-bot@users.noreply.github.com> Date: Tue, 18 Mar 2025 01:18:24 +0000 Subject: [PATCH 2/5] chore: update copyright years --- .../remark/plugins/remark-lint-html-section-structure/README.md | 2 +- .../plugins/remark-lint-html-section-structure/lib/main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md index 5ad8c7c5ad05..211e1f1fd261 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/README.md @@ -2,7 +2,7 @@ @license Apache-2.0 -Copyright (c) 2022 The Stdlib Authors. +Copyright (c) 2025 The Stdlib Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js index 3f87b5f67631..40dce3ef1214 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2022 The Stdlib Authors. +* Copyright (c) 2025 The Stdlib Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 59665e589e0b365a87544d141a9cc500feb7593d Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Sun, 20 Apr 2025 17:39:59 -0400 Subject: [PATCH 3/5] chore: address PR feedback and move repeated logic to helper functions --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: passed - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- .../lib/main.js | 290 +++++++++++------- 1 file changed, 178 insertions(+), 112 deletions(-) diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js index 40dce3ef1214..d559dc5e7da7 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js @@ -23,6 +23,8 @@ var logger = require( 'debug' ); var rule = require( 'unified-lint-rule' ); var visit = require( 'unist-util-visit' ); +var replace = require( '@stdlib/string/replace' ); +var trim = require( '@stdlib/string/trim' ); // VARIABLES // @@ -31,11 +33,125 @@ var debug = logger( 'remark-lint-html-section-structure' ); var SECTION_START = //; var SECTION_END = /<\/section>$/; var SECTION_END_WITH_COMMENT = /<\/section>\s*/; -var END_COMMENT = /^\s*/; var END_COMMENT_SIMPLE = //; +var END_COMMENT = /^\s*/; var htmlSectionStructureRule; +// FUNCTIONS // + +/** +* Creates a position string from a node's position. +* +* @private +* @param {Object} node - AST node +* @returns {string} position string in format "line:column" +*/ +function formatPosition( node ) { + return node.position.start.line + ':' + node.position.start.column; +} + +/** +* Reports an error message. +* +* @private +* @param {Object} file - virtual file +* @param {Object} node - AST node to attach message to +* @param {string} message - error message +* @param {string} errorCode - error code identifier +*/ +function reportErr( file, node, message, errorCode ) { + var fullMsg = formatPosition( node ) + ' error ' + message + ' ' + errorCode; + debug( fullMsg ); + file.message( fullMsg, node ); +} + +/** +* Extracts comment class name from a node. +* +* @private +* @param {Object} node - AST node containing comment +* @param {RegExp} matchRegex - regular expression for matching comment +* @param {RegExp} simpleMatchRegex - alternative regular expression for matching comment +* @returns {string|null} extracted class name or null if no match +*/ +function extractCommentClass( node, matchRegex, simpleMatchRegex ) { + var simpleMatch; + var match; + + match = matchRegex.exec( node.value ); + if ( match ) { + return match[ 1 ]; + } + + simpleMatch = simpleMatchRegex.exec( node.value ); + if ( simpleMatch ) { + return trim( simpleMatch[ 1 ] ); + } + + return null; +} + +/** +* Validates that the class name in a comment matches the section class. +* +* @private +* @param {Object} file - virtual file +* @param {Object} node - node containing the comment +* @param {string} cClass - class name extracted from comment +* @param {string} sClass - class name from section +* @param {string} errMsg - error message to use if validation fails +* @param {string} errCode - error code to use if validation fails +* @returns {boolean} boolean indicating whether the comment class matches the section class +*/ +function checkCommentClass( file, node, cClass, sClass, errMsg, errCode ) { + if ( sClass && cClass !== sClass ) { + debug( 'Mismatched section class in comment: expected %s, got %s', sClass, cClass ); + errMsg = replace( errMsg, '%s1', sClass ); + errMsg = replace( errMsg, '%s2', cClass ); + reportErr( file, node, errMsg, errCode ); + return false; + } + return true; +} + +/** +* Validates if there is sufficient whitespace between nodes. +* +* @private +* @param {Object} endNode - end section node +* @param {Object} commentNode - comment node +* @returns {boolean} boolean indicating whether there is sufficient whitespace between nodes +*/ +function hasValidWhitespace( endNode, commentNode ) { + var lineGap = commentNode.position.start.line - endNode.position.end.line; + debug( 'Gap between section end and comment: %d lines', lineGap ); + + // We require at least 2 lines of gap for an empty line: + return lineGap >= 2; +} + +/** +* Checks if a text node contains proper empty line. +* +* @private +* @param {Object} node - text node to check +* @returns {boolean} boolean indicating whether the node contains proper empty line +*/ +function hasEmptyLine( node ) { + var value; + + if ( node.type !== 'text' ) { + return false; + } + + value = node.value; + + // Look for at least two consecutive newlines (representing an empty line): + return (/(\r?\n)\s*(\r?\n)/).test( value ); +} + + // MAIN // /** @@ -49,28 +165,13 @@ var htmlSectionStructureRule; * @returns {void} */ function linter( tree, file, options, clbk ) { - var isValidWhitespace; - var combinedMatch; var rootSections; var sectionStack; - var commentClass; - var sectionMatch; - var simpleMatch; - var commentNode; - var commentLine; var structure; var positions; var className; - var nextNode; - var endMatch; - var endLine; - var lineGap; - var current; - var match; - var value; - var data; + var errCode; var msg; - var pos; var i; debug( 'Linting file: %s', file.path || '' ); @@ -88,10 +189,9 @@ function linter( tree, file, options, clbk ) { debug( 'Found unclosed sections: %d', sectionStack.length ); for ( i = 0; i < sectionStack.length; i++ ) { className = sectionStack[ i ].className || '[no class specified]'; - pos = sectionStack[ i ].position.start.line + ':' + sectionStack[ i ].position.start.column; - msg = pos + ' error Unclosed section tag. Missing
closing tag for section with class="' + className + '". Add a closing tag to fix. unclosed-section'; - debug( msg ); - file.message( msg, sectionStack[ i ] ); + msg = 'Unclosed section tag. Missing closing tag for section with class="' + className + '". Add a closing tag to fix.'; + errCode = 'unclosed-section'; + reportErr( file, sectionStack[ i ], msg, errCode ); } } @@ -108,21 +208,30 @@ function linter( tree, file, options, clbk ) { * @returns {void} */ function visitor( node, index, parent ) { - // Store position for tracking purposes + var combinedMatch; + var commentClass; + var sectionMatch; + var commentNode; + var nextNode; + var endMatch; + var current; + var match; + var data; + + // Store position for tracking purposes: if ( node.position ) { positions[ node.position.start.line ] = node; } - // Check if this is a combined section end with comment on the same line (which is an error) + // Check if this is a combined section end with comment on the same line (which is an error): combinedMatch = SECTION_END_WITH_COMMENT.exec( node.value ); if ( combinedMatch ) { debug( 'Found combined section end with comment: %s', node.value ); - pos = node.position.start.line + ':' + node.position.start.column; - msg = pos + ' error Missing empty line between closing section tag and comment. There should be an empty line between and missing-empty-line'; - debug( msg ); - file.message( msg, node ); + msg = 'Missing empty line between closing section tag and comment. There should be an empty line between and '; + errCode = 'missing-empty-line'; + reportErr( file, node, msg, errCode ); - // Make sure to pop the section stack for this case - otherwise we'll get "unclosed section" errors + // Make sure to pop the section stack for this case - otherwise we'll get "unclosed section" errors: if ( sectionStack.length > 0 ) { debug( 'Popping section stack for combined section end+comment' ); sectionStack.pop(); @@ -145,80 +254,55 @@ function linter( tree, file, options, clbk ) { }; if ( sectionStack.length === 0 ) { - // This is a root level section + // This is a root level section... rootSections.push( data ); structure.push( data ); } else { - // This is a nested section, add to parent's children - sectionStack[sectionStack.length - 1].children.push( data ); + // This is a nested section, add to parent's children: + sectionStack[ sectionStack.length - 1 ].children.push( data ); } sectionStack.push( data ); return; } - // Check if this is a section end tag + // Check if this is a section end tag: endMatch = SECTION_END.exec( node.value ); if ( endMatch ) { debug( 'Found a section end tag' ); if ( sectionStack.length === 0 ) { debug( 'Orphaned section end tag found' ); - pos = node.position.start.line + ':' + node.position.start.column; - msg = pos + ' error Orphaned section closing tag. Found without a matching opening
tag. Remove this tag or add a corresponding opening tag. orphaned-section-end'; - debug( msg ); - file.message( msg, node ); + msg = 'Orphaned section closing tag. Found
without a matching opening
tag. Remove this tag or add a corresponding opening tag.'; + errCode = 'orphaned-section-end'; + reportErr( file, node, msg, errCode ); return; } - // Special handling for direct closing tag followed by a comment (no text node between) + // Special handling for direct closing tag followed by a comment (no text node between)... if ( index + 1 < parent.children.length ) { nextNode = parent.children[index + 1]; - // Look at the next node to check if it's a comment + // Look at the next node to check if it's a comment: if ( nextNode.type === 'html' ) { // Check if the next node is a comment but NOT on the same line - // This avoids double-reporting with the SECTION_END_WITH_COMMENT check above - match = END_COMMENT.exec( nextNode.value ); - simpleMatch = END_COMMENT_SIMPLE.exec( nextNode.value ); + commentClass = extractCommentClass( nextNode, END_COMMENT, END_COMMENT_SIMPLE ); - // If either regex matches, we've found a comment - if ( match || simpleMatch ) { + if ( commentClass ) { // Check if there are at least 2 line breaks between them - endLine = node.position.end.line; - commentLine = nextNode.position.start.line; - lineGap = commentLine - endLine; - - debug( 'Gap between section end and comment: %d lines', lineGap ); - - // We require at least 2 lines of gap for an empty line - if ( lineGap < 2 ) { - pos = node.position.start.line + ':' + node.position.start.column; - msg = pos + ' error Missing proper empty line after closing section tag. There should be an empty line between
and the closing comment. missing-empty-line-after-section'; - debug( msg ); - file.message( msg, node ); + if ( !hasValidWhitespace( node, nextNode ) ) { + msg = 'Missing proper empty line after closing section tag. There should be an empty line between and the closing comment.'; + errCode = 'missing-empty-line-after-section'; + reportErr( file, node, msg, errCode ); } // Verify the comment matches the class name: current = sectionStack[sectionStack.length - 1]; + msg = 'Mismatched section class in closing comment. Opening tag has class="%s1" but closing comment is . They should match.'; + errCode = 'mismatched-section-class'; + checkCommentClass( file, nextNode, commentClass, current.className, msg, errCode ); - // Get class from whichever regex matched: - if ( match ) { - commentClass = match[1]; - } else { - commentClass = simpleMatch[1].trim(); - } - - // Verify the comment matches the class name: - if ( current.className && commentClass !== current.className ) { - debug( 'Mismatched section class in comment: expected %s, got %s', current.className, commentClass ); - pos = nextNode.position.start.line + ':' + nextNode.position.start.column; - msg = pos + ' error Mismatched section class in closing comment. Opening tag has class="' + current.className + '" but closing comment is . They should match. mismatched-section-class'; - debug( msg ); - file.message( msg, nextNode ); - } - - // Pop the section stack before returning + // Pop the section stack before returning: sectionStack.pop(); return; } @@ -231,62 +315,44 @@ function linter( tree, file, options, clbk ) { commentNode = parent.children[index + 2]; // Check if the next node is a proper whitespace with empty line: - isValidWhitespace = false; - if (nextNode.type === 'text') { - value = nextNode.value; - - // Look for at least two consecutive newlines (representing an empty line): - isValidWhitespace = ((/(\r?\n)\s*(\r?\n)/).test(value)); - } - - if ( !isValidWhitespace ) { + if ( !hasEmptyLine( nextNode ) ) { debug( 'Invalid whitespace after section end' ); - pos = node.position.start.line + ':' + node.position.start.column; - msg = pos + ' error Missing proper empty line after closing section tag. There should be an empty line between and the closing comment. missing-empty-line-after-section'; - debug( msg ); - file.message( msg, node ); + msg = 'Missing proper empty line after closing section tag. There should be an empty line between and the closing comment.'; + errCode = 'missing-empty-line-after-section'; + reportErr( file, node, msg, errCode ); } - // Check if the following node is the expected comment + // Check if the following node is the expected comment: if ( commentNode.type === 'html' ) { match = END_COMMENT.exec( commentNode.value ); if ( match ) { - current = sectionStack[sectionStack.length - 1]; - - // Verify the comment matches the class name - if ( current.className && match[1] !== current.className ) { - debug( 'Mismatched section class in comment: expected %s, got %s', current.className, match[1] ); - pos = commentNode.position.start.line + ':' + commentNode.position.start.column; - msg = pos + ' error Mismatched section class in closing comment. Opening tag has class="' + current.className + '" but closing comment is . They should match. mismatched-section-class'; - debug( msg ); - file.message( msg, commentNode ); - } + current = sectionStack[ sectionStack.length - 1 ]; + msg = 'Mismatched section class in closing comment. Opening tag has class="%s1" but closing comment is . They should match.'; + errCode = 'mismatched-section-class'; + checkCommentClass( file, commentNode, match[1], current.className, msg, errCode ); } else { - // The HTML node exists but it's not a proper closing comment + // The HTML node exists but it's not a proper closing comment... debug( 'Invalid closing comment format' ); - pos = commentNode.position.start.line + ':' + commentNode.position.start.column; - msg = pos + ' error Invalid section closing comment format. Expected invalid-closing-comment'; - debug( msg ); - file.message( msg, commentNode ); + msg = 'Invalid section closing comment format. Expected '; + errCode = 'invalid-closing-comment'; + reportErr( file, commentNode, msg, errCode ); } } else { - // The node after whitespace is not an HTML node (comment) + // The node after whitespace is not an HTML node (comment)... debug( 'Node after whitespace is not an HTML comment' ); - pos = node.position.start.line + ':' + node.position.start.column; - msg = pos + ' error Missing section closing comment. Section closing tag should be followed by comment. missing-section-comment'; - debug( msg ); - file.message( msg, node ); + msg = 'Missing section closing comment. Section closing tag should be followed by comment.'; + errCode = 'missing-section-comment'; + reportErr( file, node, msg, errCode ); } } else { - // There's not enough nodes after the section closing tag + // There's not enough nodes after the section closing tag... debug( 'Not enough nodes after section end' ); - pos = node.position.start.line + ':' + node.position.start.column; - msg = pos + ' error Missing section closing comment. Section closing tag should be followed by comment on a separate line. missing-section-comment'; - debug( msg ); - file.message( msg, node ); + msg = 'Missing section closing comment. Section closing tag should be followed by comment on a separate line.'; + errCode = 'missing-section-comment'; + reportErr( file, node, msg, errCode ); } - // Pop the section stack + // Pop the section stack: sectionStack.pop(); } } From 62f6e820a918674f7d0f53df6d54cfd167b10107 Mon Sep 17 00:00:00 2001 From: Athan Date: Tue, 22 Apr 2025 20:52:34 -0700 Subject: [PATCH 4/5] Apply suggestions from code review Signed-off-by: Athan --- .../lib/main.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js index d559dc5e7da7..568c513b9edc 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js @@ -73,7 +73,7 @@ function reportErr( file, node, message, errorCode ) { * @param {Object} node - AST node containing comment * @param {RegExp} matchRegex - regular expression for matching comment * @param {RegExp} simpleMatchRegex - alternative regular expression for matching comment -* @returns {string|null} extracted class name or null if no match +* @returns {(string|null)} extracted class name or null if no match */ function extractCommentClass( node, matchRegex, simpleMatchRegex ) { var simpleMatch; @@ -239,7 +239,7 @@ function linter( tree, file, options, clbk ) { return; } - // Check if this is a section start tag + // Check if this is a section start tag: sectionMatch = SECTION_START.exec( node.value ); if ( sectionMatch ) { debug( 'Found a section start tag with class: %s', sectionMatch[1] || '[none]' ); @@ -281,7 +281,7 @@ function linter( tree, file, options, clbk ) { // Special handling for direct closing tag followed by a comment (no text node between)... if ( index + 1 < parent.children.length ) { - nextNode = parent.children[index + 1]; + nextNode = parent.children[ index + 1 ]; // Look at the next node to check if it's a comment: if ( nextNode.type === 'html' ) { @@ -289,7 +289,7 @@ function linter( tree, file, options, clbk ) { commentClass = extractCommentClass( nextNode, END_COMMENT, END_COMMENT_SIMPLE ); if ( commentClass ) { - // Check if there are at least 2 line breaks between them + // Check if there are at least 2 line breaks between them: if ( !hasValidWhitespace( node, nextNode ) ) { msg = 'Missing proper empty line after closing section tag. There should be an empty line between and the closing comment.'; errCode = 'missing-empty-line-after-section'; @@ -311,8 +311,8 @@ function linter( tree, file, options, clbk ) { // Standard handling for other cases with whitespace between section end and comment... if ( index + 2 < parent.children.length ) { - nextNode = parent.children[index + 1]; - commentNode = parent.children[index + 2]; + nextNode = parent.children[ index + 1 ]; + commentNode = parent.children[ index + 2 ]; // Check if the next node is a proper whitespace with empty line: if ( !hasEmptyLine( nextNode ) ) { @@ -333,21 +333,21 @@ function linter( tree, file, options, clbk ) { } else { // The HTML node exists but it's not a proper closing comment... debug( 'Invalid closing comment format' ); - msg = 'Invalid section closing comment format. Expected '; + msg = 'Invalid section closing comment format. Expected '; errCode = 'invalid-closing-comment'; reportErr( file, commentNode, msg, errCode ); } } else { // The node after whitespace is not an HTML node (comment)... debug( 'Node after whitespace is not an HTML comment' ); - msg = 'Missing section closing comment. Section closing tag should be followed by comment.'; + msg = 'Missing section closing comment. Section closing tag should be followed by comment.'; errCode = 'missing-section-comment'; reportErr( file, node, msg, errCode ); } } else { // There's not enough nodes after the section closing tag... debug( 'Not enough nodes after section end' ); - msg = 'Missing section closing comment. Section closing tag should be followed by comment on a separate line.'; + msg = 'Missing section closing comment. Section closing tag should be followed by comment on a separate line.'; errCode = 'missing-section-comment'; reportErr( file, node, msg, errCode ); } From 12e5511e7f7815fae50a9b00f873861216394a63 Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Thu, 24 Apr 2025 17:54:21 -0400 Subject: [PATCH 5/5] chore: rename variables and define empty line regexp --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed --- --- .../lib/main.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js index 568c513b9edc..9163f5f4cc2c 100644 --- a/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js +++ b/lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-html-section-structure/lib/main.js @@ -30,11 +30,12 @@ var trim = require( '@stdlib/string/trim' ); // VARIABLES // var debug = logger( 'remark-lint-html-section-structure' ); -var SECTION_START = //; -var SECTION_END = /<\/section>$/; -var SECTION_END_WITH_COMMENT = /<\/section>\s*/; -var END_COMMENT_SIMPLE = //; -var END_COMMENT = /^\s*/; +var RE_SECTION_START = //; +var RE_SECTION_END = /<\/section>$/; +var RE_SECTION_END_WITH_COMMENT = /<\/section>\s*/; +var RE_END_COMMENT_SIMPLE = //; +var RE_END_COMMENT = /^\s*/; +var RE_EMPTY_LINES = /(\r?\n)\s*(\r?\n)/; var htmlSectionStructureRule; @@ -148,7 +149,7 @@ function hasEmptyLine( node ) { value = node.value; // Look for at least two consecutive newlines (representing an empty line): - return (/(\r?\n)\s*(\r?\n)/).test( value ); + return RE_EMPTY_LINES.test( value ); } @@ -224,7 +225,7 @@ function linter( tree, file, options, clbk ) { } // Check if this is a combined section end with comment on the same line (which is an error): - combinedMatch = SECTION_END_WITH_COMMENT.exec( node.value ); + combinedMatch = RE_SECTION_END_WITH_COMMENT.exec( node.value ); if ( combinedMatch ) { debug( 'Found combined section end with comment: %s', node.value ); msg = 'Missing empty line between closing section tag and comment. There should be an empty line between and '; @@ -240,7 +241,7 @@ function linter( tree, file, options, clbk ) { } // Check if this is a section start tag: - sectionMatch = SECTION_START.exec( node.value ); + sectionMatch = RE_SECTION_START.exec( node.value ); if ( sectionMatch ) { debug( 'Found a section start tag with class: %s', sectionMatch[1] || '[none]' ); @@ -267,7 +268,7 @@ function linter( tree, file, options, clbk ) { } // Check if this is a section end tag: - endMatch = SECTION_END.exec( node.value ); + endMatch = RE_SECTION_END.exec( node.value ); if ( endMatch ) { debug( 'Found a section end tag' ); @@ -286,7 +287,7 @@ function linter( tree, file, options, clbk ) { // Look at the next node to check if it's a comment: if ( nextNode.type === 'html' ) { // Check if the next node is a comment but NOT on the same line - commentClass = extractCommentClass( nextNode, END_COMMENT, END_COMMENT_SIMPLE ); + commentClass = extractCommentClass( nextNode, RE_END_COMMENT, RE_END_COMMENT_SIMPLE ); if ( commentClass ) { // Check if there are at least 2 line breaks between them: @@ -324,7 +325,7 @@ function linter( tree, file, options, clbk ) { // Check if the following node is the expected comment: if ( commentNode.type === 'html' ) { - match = END_COMMENT.exec( commentNode.value ); + match = RE_END_COMMENT.exec( commentNode.value ); if ( match ) { current = sectionStack[ sectionStack.length - 1 ]; msg = 'Mismatched section class in closing comment. Opening tag has class="%s1" but closing comment is . They should match.';