Skip to content

Commit 859d608

Browse files
committed
build: add options validation
--- 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: 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 ---
1 parent f913da8 commit 859d608

File tree

9 files changed

+541
-266
lines changed

9 files changed

+541
-266
lines changed

lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-expected-html-sections/README.md

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,35 @@ function done( error, file ) {
5454
}
5555
```
5656

57-
#### options.schema
57+
The plugin accepts the following `options`:
5858

59-
The plugin can be configured with a custom schema that defines required and optional sections.
59+
- **schema**: schema for expected HTML sections.
60+
61+
The default schema requires `usage`, `examples`, and `links` at the root level, and if a `c` section exists, it requires `usage` and `examples` subsections.
62+
63+
To specify a custom schema, set the `schema` option.
6064

6165
```javascript
6266
var remark = require( 'remark' );
6367

64-
var opts = {
65-
'schema': {
66-
'root': {
67-
'required': [ 'usage', 'examples', 'links' ],
68-
'optional': [ 'intro', 'notes', 'c', 'references', 'related' ]
69-
},
70-
'c': {
71-
'required': [ 'usage', 'examples' ],
72-
'optional': [ 'intro', 'notes' ]
73-
}
68+
var customSchema = {
69+
'root': {
70+
'required': [ 'usage', 'examples', 'links', 'related' ],
71+
'optional': [ 'intro', 'notes', 'c', 'references' ]
7472
}
7573
};
74+
var opts = {
75+
'schema': customSchema
76+
};
77+
remark().use( expectedSections, opts ).process( '# README', done );
7678

77-
remark().use( expectedSections, opts ).process( src, done );
79+
function done( error, file ) {
80+
if ( error ) {
81+
console.error( error );
82+
}
83+
}
7884
```
7985

80-
The default schema requires `usage`, `examples`, and `links` at the root level, and if a `c` section exists, it requires `usage` and `examples` subsections.
81-
8286
</section>
8387

8488
<!-- /.usage -->
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var copy = require( '@stdlib/utils/copy' );
24+
var defaults = require( './defaults.json' );
25+
var validate = require( './validate.js' );
26+
var linter = require( './linter.js' );
27+
28+
29+
// MAIN //
30+
31+
/**
32+
* Attaches a plugin to a remark processor to lint expected HTML sections.
33+
*
34+
* @param {Options} [options] - plugin options
35+
* @param {Object} [options.schema] - schema for expected HTML sections
36+
* @throws {TypeError} options argument must be an object
37+
* @throws {TypeError} must provide valid options
38+
* @returns {Function} transform function
39+
*
40+
* @example
41+
* var remark = require( 'remark' );
42+
*
43+
* var str = [
44+
* '<section class="usage">',
45+
* '',
46+
* '## Usage',
47+
* '',
48+
* '</section>',
49+
* '',
50+
* '<!-- /.usage -->',
51+
* '',
52+
* '<section class="examples">',
53+
* '',
54+
* '## Examples',
55+
* '',
56+
* '</section>',
57+
* '',
58+
* '<!-- /.examples -->',
59+
* ''
60+
* ].join( '\n' );
61+
*
62+
* remark().use( lint ).process( str, done );
63+
*
64+
* function done( error ) {
65+
* if ( error ) {
66+
* throw error;
67+
* }
68+
* }
69+
*/
70+
function attacher( options ) {
71+
var opts;
72+
var err;
73+
74+
// Set default options:
75+
opts = copy( defaults );
76+
77+
// NOTE: cannot use `arguments.length` check, as `options` may be explicitly passed as `undefined`
78+
if ( options !== void 0 ) {
79+
err = validate( opts, options );
80+
if ( err ) {
81+
throw err;
82+
}
83+
}
84+
return linter( opts );
85+
}
86+
87+
88+
// EXPORTS //
89+
90+
module.exports = attacher;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"schema": {
3+
"root": {
4+
"required": [
5+
"usage",
6+
"examples",
7+
"links"
8+
],
9+
"optional": [
10+
"intro",
11+
"notes",
12+
"c",
13+
"references",
14+
"related"
15+
]
16+
},
17+
"c": {
18+
"required": [
19+
"usage",
20+
"examples"
21+
],
22+
"optional": [
23+
"intro",
24+
"notes"
25+
]
26+
}
27+
}
28+
}

lib/node_modules/@stdlib/_tools/remark/plugins/remark-lint-expected-html-sections/lib/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @license Apache-2.0
33
*
4-
* Copyright (c) 2022 The Stdlib Authors.
4+
* Copyright (c) 2025 The Stdlib Authors.
55
*
66
* Licensed under the Apache License, Version 2.0 (the "License");
77
* you may not use this file except in compliance with the License.
@@ -32,9 +32,9 @@
3232

3333
// MODULES //
3434

35-
var main = require( './main.js' );
35+
var attacher = require( './attacher.js' );
3636

3737

3838
// EXPORTS //
3939

40-
module.exports = main;
40+
module.exports = attacher;
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
'use strict';
20+
21+
// MODULES //
22+
23+
var logger = require( 'debug' );
24+
var visit = require( 'unist-util-visit' );
25+
var keys = require( '@stdlib/utils/keys' );
26+
27+
28+
// VARIABLES //
29+
30+
var debug = logger( 'remark-lint-expected-html-sections' );
31+
var SECTION_START = /<section(?:\s+class="([^"]*)")?>/;
32+
33+
34+
// MAIN //
35+
36+
/**
37+
* Returns a linter function.
38+
*
39+
* @private
40+
* @param {Object} options - linter options
41+
* @param {Object} [options.schema] - section schema defining required and optional sections
42+
* @returns {Function} linter function
43+
*/
44+
function factory( options ) {
45+
return linter;
46+
47+
/**
48+
* Validates HTML section hierarchy in README files according to stdlib conventions.
49+
*
50+
* @private
51+
* @param {Node} tree - abstract syntax tree (AST)
52+
* @param {File} file - virtual file
53+
* @returns {void}
54+
*/
55+
function linter( tree, file ) {
56+
var requiredRootSections;
57+
var requiredCSections;
58+
var sectionStructure;
59+
var currentSection;
60+
var sectionsFound;
61+
var sectionStack;
62+
var sectionMatch;
63+
var missingRoot;
64+
var className;
65+
var missingC;
66+
var schema;
67+
var msg;
68+
var i;
69+
70+
debug( 'Linting file: %s', file.path || '' );
71+
schema = options.schema;
72+
73+
// Initialize section tracking:
74+
sectionStack = [];
75+
sectionStructure = [];
76+
77+
// Keep track of sections at different levels:
78+
sectionsFound = {
79+
'root': {},
80+
'c': {}
81+
};
82+
83+
// Visit all HTML nodes to build section structure:
84+
visit( tree, 'html', visitor );
85+
86+
// Log all sections found for debugging:
87+
debug( 'Root sections found: %j', keys( sectionsFound.root ) );
88+
if ( sectionsFound.root.c ) {
89+
debug( 'C sections found: %j', keys( sectionsFound.c ) );
90+
}
91+
92+
// After visiting all nodes, validate against schema:
93+
requiredRootSections = schema.root.required || [];
94+
95+
// Check for missing required root sections:
96+
missingRoot = [];
97+
for ( i = 0; i < requiredRootSections.length; i++ ) {
98+
if ( !sectionsFound.root[ requiredRootSections[ i ] ] ) {
99+
missingRoot.push( requiredRootSections[ i ] );
100+
}
101+
}
102+
103+
if ( missingRoot.length > 0 ) {
104+
msg = 'Missing required root-level sections: `' + missingRoot.join( '`, `' ) + '`. Required sections are: `' + requiredRootSections.join('`, `') + '`. missing-required-sections';
105+
debug( msg );
106+
file.message( msg, tree );
107+
}
108+
109+
// If 'c' section exists, check its requirements:
110+
if ( sectionsFound.root.c ) {
111+
requiredCSections = (schema.c) ? (schema.c.required || []) : [];
112+
113+
// Check for missing required C sections:
114+
missingC = [];
115+
for ( i = 0; i < requiredCSections.length; i++ ) {
116+
if ( !sectionsFound.c[ requiredCSections[ i ] ] ) {
117+
missingC.push( requiredCSections[ i ] );
118+
}
119+
}
120+
121+
if ( missingC.length > 0 ) {
122+
msg = 'Missing required sections in "c" section: `' + missingC.join('`, `') + '`. Required C sections are: `' + requiredCSections.join('`, `') + '`. missing-required-c-sections';
123+
debug( msg );
124+
file.message( msg, sectionsFound.root.c.node );
125+
}
126+
}
127+
128+
debug( 'Finished linting: %s', file.path || '' );
129+
130+
/**
131+
* Callback invoked upon finding a matching node.
132+
*
133+
* @private
134+
* @param {Object} node - AST node
135+
* @returns {void}
136+
*/
137+
function visitor( node ) {
138+
// Check if this is a section start tag:
139+
sectionMatch = SECTION_START.exec( node.value );
140+
if ( sectionMatch ) {
141+
className = sectionMatch[1] || '';
142+
143+
debug( 'Found section with class: %s', className );
144+
145+
// Create section data object:
146+
currentSection = {
147+
'node': node,
148+
'name': className,
149+
'children': [],
150+
'parent': null
151+
};
152+
153+
// Add to parent if there is one:
154+
if ( sectionStack.length > 0 ) {
155+
currentSection.parent = sectionStack[ sectionStack.length - 1 ];
156+
currentSection.parent.children.push( currentSection );
157+
158+
// Record C-level section:
159+
if ( currentSection.parent.name === 'c' ) {
160+
sectionsFound.c[ className ] = currentSection;
161+
}
162+
} else {
163+
// This is a root section:
164+
sectionStructure.push( currentSection );
165+
sectionsFound.root[ className ] = currentSection;
166+
}
167+
168+
// Push to stack:
169+
sectionStack.push( currentSection );
170+
}
171+
// Check if this is a section end tag:
172+
else if ( /<\/section>/.test( node.value ) ) {
173+
if ( sectionStack.length > 0 ) {
174+
sectionStack.pop();
175+
}
176+
}
177+
}
178+
}
179+
}
180+
181+
182+
// EXPORTS //
183+
184+
module.exports = factory;

0 commit comments

Comments
 (0)