From 640226d4aa0174efbf374b8d2d37d4cfd5e58e3f Mon Sep 17 00:00:00 2001 From: Kanika Date: Sun, 20 Sep 2026 17:40:13 +0530 Subject: [PATCH] feat: enforce primary header filenames --- docs/contributing/packages.md | 1 + .../_tools/lint/header-filenames/lib/lint.js | 39 ++++++++++++ .../lint/header-filenames/test/test.cli.js | 2 +- .../lint/header-filenames/test/test.lint.js | 60 +++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/docs/contributing/packages.md b/docs/contributing/packages.md index d8f963f60706..1789d4e169d6 100644 --- a/docs/contributing/packages.md +++ b/docs/contributing/packages.md @@ -34,6 +34,7 @@ Every package in stdlib has, at minimum, the following structure: ```text benchmark/ benchmarks +include/ public C/C++ header files docs/ documentation (excluding README) examples/ examples lib/ package implementation diff --git a/lib/node_modules/@stdlib/_tools/lint/header-filenames/lib/lint.js b/lib/node_modules/@stdlib/_tools/lint/header-filenames/lib/lint.js index 822aa2595dde..df402129b4f7 100644 --- a/lib/node_modules/@stdlib/_tools/lint/header-filenames/lib/lint.js +++ b/lib/node_modules/@stdlib/_tools/lint/header-filenames/lib/lint.js @@ -39,6 +39,7 @@ var CPP_HEADERS = require( './cpp_headers.json' ); * @returns {(ObjectArray|EmptyArray)} array of lint errors */ function lint( arr ) { + var packages; var headers; var fnames; var fname; @@ -50,10 +51,38 @@ function lint( arr ) { var i; var j; var k; + var name; + var include; + var pkg; + var expected; + var packagePath; + var packageNames; headers = {}; + packages = {}; out = []; for ( i = 0; i < arr.length; i++ ) { + name = arr[ i ].replace( /\\/g, '/' ); + if ( name.indexOf( '/include/' ) !== -1 ) { + include = name.substring( 0, name.indexOf( '/include/' )+9 ); + pkg = basename( include.substring( 0, include.length-9 ) ); + if ( pkg !== '.' && pkg !== '..' && !/^[A-Za-z]:\//.test( name ) ) { + expected = pkg.replace( /-/g, '_' )+'.h'; + if ( include.indexOf( '/@stdlib/' ) !== -1 ) { + packagePath = include.substring( include.indexOf( '/@stdlib/' )+9, include.length-9 ); + expected = 'stdlib/'+packagePath.slice( 0, -pkg.length )+pkg.replace( /-/g, '_' )+'.h'; + } + if ( !packages[ include ] ) { + packages[ include ] = { + 'path': include+expected, + 'found': false + }; + } + if ( name === packages[ include ].path || name === packages[ include ].path.replace( /\\.h$/, '.hpp' ) ) { + packages[ include ].found = true; + } + } + } fname = basename( arr[ i ] ); ext = extname( fname ); fname = fname.substring( 0, fname.length-ext.length ); @@ -81,6 +110,16 @@ function lint( arr ) { headers[ ext ][ fname ] = [ arr[ i ] ]; } } + // Ensure every package has a primary header file... + packageNames = objectKeys( packages ); + for ( i = 0; i < packageNames.length; i++ ) { + if ( !packages[ packageNames[ i ] ].found ) { + out.push({ + 'name': packages[ packageNames[ i ] ].path, + 'error': 'missing primary header file.' + }); + } + } // For every basename found multiple times, log a lint error... exts = objectKeys( headers ); for ( i = 0; i < exts.length; i++ ) { diff --git a/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.cli.js b/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.cli.js index e7ac89440584..63cd5502a20b 100644 --- a/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.cli.js +++ b/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.cli.js @@ -191,7 +191,7 @@ tape( 'the command-line interface prints newline-delimited JSON (NDJSON) if head t.strictEqual( stdout.toString(), '', 'does not print to `stdout`' ); results = stderr.toString().split( RE_EOL ); - t.strictEqual( results.length, 3, 'returns expected number of errors' ); + t.strictEqual( results.length, 4, 'returns expected number of errors' ); for ( i = 0; i < results.length-1; i++ ) { t.strictEqual( isJSON( results[i] ), true, 'returns JSON' ); } diff --git a/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.lint.js b/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.lint.js index 914393c6cc81..b8be92f89ac3 100644 --- a/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.lint.js +++ b/lib/node_modules/@stdlib/_tools/lint/header-filenames/test/test.lint.js @@ -118,3 +118,63 @@ tape( 'the function returns an empty array if all header base filenames are vali t.strictEqual( names.length, 0, 'returns expected value' ); t.end(); }); + +tape( 'the function returns an error if a package is missing its primary header file', function test( t ) { + var names; + + names = lint([ + 'foo/include/private.h' + ]); + + t.strictEqual( names.length, 1, 'returns one error' ); + t.strictEqual( names[ 0 ].name, 'foo/include/foo.h', 'returns expected filename' ); + t.strictEqual( names[ 0 ].error, 'missing primary header file.', 'returns expected error' ); + t.end(); +}); + +tape( 'the function allows additional header files with arbitrary names', function test( t ) { + var names; + + names = lint([ + 'foo/include/foo.h', + 'foo/include/private.h' + ]); + + t.strictEqual( names.length, 0, 'returns an empty array' ); + t.end(); +}); + +tape( 'the function converts hyphens in a package path to underscores when resolving the primary header file', function test( t ) { + var names; + + names = lint([ + 'lib/node_modules/@stdlib/math/base/assert/int32-is-even/include/stdlib/math/base/assert/int32_is_even.h' + ]); + + t.strictEqual( names.length, 0, 'returns an empty array' ); + t.end(); +}); + +tape( 'the function does not convert hyphens in parent package directories when resolving the primary header file', function test( t ) { + var names; + + names = lint([ + 'lib/node_modules/@stdlib/foo-bar/my-package/include/stdlib/foo-bar/my_package.h' + ]); + + t.strictEqual( names.length, 0, 'returns an empty array' ); + t.end(); +}); + +tape( 'the function returns an error if the primary header filename is invalid', function test( t ) { + var names; + + names = lint([ + 'lib/node_modules/@stdlib/foo-bar/my-package/include/stdlib/foo-bar/wrong_name.h' + ]); + + t.strictEqual( names.length, 1, 'returns one error' ); + t.strictEqual( names[ 0 ].name, 'lib/node_modules/@stdlib/foo-bar/my-package/include/stdlib/foo-bar/my_package.h', 'returns expected filename' ); + t.strictEqual( names[ 0 ].error, 'missing primary header file.', 'returns expected error' ); + t.end(); +});