Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions .github/workflows/lint_changed_files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,24 @@ jobs:
- name: 'Lint Markdown files'
if: success() || failure()
run: |
files=$(echo "${{ steps.changed-files.outputs.files }}" | tr ' ' '\n' | grep -E '\.md$' | grep -v '^\.github/workflows/.*\.md$' | tr '\n' ' ' | sed 's/ $//')
files=$(echo "${{ steps.changed-files.outputs.files }}" | tr ' ' '\n' | grep '\.md$' | tr '\n' ' ' | sed 's/ $//')
if [ -n "${files}" ]; then
make lint-markdown-files FILES="${files}"
# Partition Markdown files by scope to match remark configs:
pkg_readme_files=$(echo "${files}" | tr ' ' '\n' | grep '^lib/node_modules/@stdlib/.*/README\.md$' | tr '\n' ' ' | sed 's/ $//' || true)
docs_files=$(echo "${files}" | tr ' ' '\n' | grep '^docs/' | tr '\n' ' ' | sed 's/ $//' || true)
other_files=$(echo "${files}" | tr ' ' '\n' | grep -v '\.github/workflows/.*\.md$' | grep -v 'lib/node_modules/@stdlib/.*/README\.md$' | grep -v '^docs/' | tr '\n' ' ' | sed 's/ $//' || true)

status=0
if [ -n "${pkg_readme_files}" ]; then
make lint-markdown-package-readme-files FILES="${pkg_readme_files}" || status=$?
fi
if [ -n "${docs_files}" ]; then
make lint-markdown-docs-files FILES="${docs_files}" || status=$?
fi
if [ -n "${other_files}" ]; then
make lint-markdown-files FILES="${other_files}" || status=$?
fi
exit $status
fi

# Lint shell script files:
Expand Down
19 changes: 17 additions & 2 deletions .github/workflows/lint_random_files.yml
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,24 @@ jobs:
- name: 'Lint Markdown files'
if: ( github.event.inputs.markdown != 'false' ) && ( success() || failure() )
run: |
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep -E '\.md$' | tr '\n' ' ')
files=$(echo "${{ steps.random-files.outputs.files }}" | tr ',' '\n' | grep '\.md$' | tr '\n' ' ')
if [ -n "${files}" ]; then
make lint-markdown-files FAST_FAIL=0 FILES="${files}"
# Partition Markdown files by scope to match remark configs:
pkg_readme_files=$(echo "${files}" | tr ' ' '\n' | grep '^lib/node_modules/@stdlib/.*/README\.md$' | tr '\n' ' ' | sed 's/ $//' || true)
docs_files=$(echo "${files}" | tr ' ' '\n' | grep '^docs/' | tr '\n' ' ' | sed 's/ $//' || true)
other_files=$(echo "${files}" | tr ' ' '\n' | grep -v 'lib/node_modules/@stdlib/.*/README\.md$' | grep -v '^docs/' | tr '\n' ' ' | sed 's/ $//' || true)

status=0
if [ -n "${pkg_readme_files}" ]; then
make lint-markdown-package-readme-files FAST_FAIL=0 FILES="${pkg_readme_files}" || status=$?
fi
if [ -n "${docs_files}" ]; then
make lint-markdown-docs-files FAST_FAIL=0 FILES="${docs_files}" || status=$?
fi
if [ -n "${other_files}" ]; then
make lint-markdown-files FAST_FAIL=0 FILES="${other_files}" || status=$?
fi
exit $status
fi

# Lint package.json files:
Expand Down
30 changes: 30 additions & 0 deletions etc/remark/.remarkrc.docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2026 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';

// MAIN //

var config = {
'plugins': require( './plugins/docs.js' )
};


// EXPORTS //

module.exports = config;
2 changes: 1 addition & 1 deletion etc/remark/.remarkrc.js
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, this is intentional? Meaning, for other Markdown files, we want to use list of plugins used for docs?

That is possible, if we assume that the docs config is effectively a subset of all the plugins we want to apply.

Copy link
Copy Markdown
Member Author

@batpigandme batpigandme Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. So this was a question I brought up in #11121 (comment).

My question is whether we want to apply the full set of plugins/default REMARK_CONF to all markdown files EXCEPT those in /docs, or if we want to apply the full plugins set ONLY to those files matching lib/node_modules/@stdlib/.

By default, remark uses all of the plugins in the plugins subfolder (which includes linters and processors). Because these plugins were designed to work with the very structured package READMEs, this includes things like linting for the expected HTML sections.

So, you're right, the /docs config is just applying a subset. Right now we're basically saying "if it's not a package README, don't expect the HTML sections to be there" (we went even stricter than my original proposal where everything in lib/node_modules/@stdlib would get the full processing).

I definitely think it's weird (and unintuitive) to have the /docs setup be, effectively, the default. But, we also don't expect those HTML sections in the root documentation.

We could do a rename, or we could not use /docs as the default.

We already have them in three buckets (package READMEs, docs, and other), so it's easy enough to do (it's just that "other" currently gets the same treatment as docs).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dug into the transformer plugins (equations, namespace-toc, related, pkg-urls, stdlib-urls). None of them actually load via .remarkrc.*— each recipe attaches them via --use flags. So, they only ever touch package READMEs (7,291 READMEs have <equation> tags; zero non-READMEs do; the other recipes scope explicitly to READMEs via find ... -name 'README.md' or list-pkgs-namespace-readmes).

So .remarkrc.js-as-docs-subset doesn't route through any transformer plugin. The design question is purely about lint ergonomics for non-README, non-/docs markdown.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// MAIN //

var config = {
'plugins': require( './plugins' )
'plugins': require( './plugins/base.js' )
};


Expand Down
30 changes: 30 additions & 0 deletions etc/remark/.remarkrc.pkg_readmes.js
Comment thread
batpigandme marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2026 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';

// MAIN //

var config = {
'plugins': require( './plugins/pkg_readmes.js' )
};


// EXPORTS //

module.exports = config;
2 changes: 0 additions & 2 deletions etc/remark/plugins/index.js → etc/remark/plugins/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ plugins.push( require( './frontmatter' ) );
plugins = plugins.concat( require( './lint' ) );
plugins.push( require( './eslint' ) );
plugins.push( require( './lint-equations' ) );
plugins.push( require( './lint-expected-html-sections' ) );
plugins.push( require( './lint-html-section-structure' ) );
plugins.push( require( './validate-links' ) );


Expand Down
28 changes: 28 additions & 0 deletions etc/remark/plugins/docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2026 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';

// MAIN //

var plugins = require( './base.js' );


// EXPORTS //

module.exports = plugins;
32 changes: 32 additions & 0 deletions etc/remark/plugins/pkg_readmes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2026 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';

// MAIN //

var plugins = [];

plugins = plugins.concat( require( './base.js' ) );
plugins.push( require( './lint-expected-html-sections' ) );
plugins.push( require( './lint-html-section-structure' ) );


// EXPORTS //

module.exports = plugins;
42 changes: 40 additions & 2 deletions tools/git/hooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,48 @@ run_lint() {
else
task_status 'skipped'
fi
# Lint Markdown files...
# Lint package README Markdown files...
add_task 'lint_markdown_pkg_readmes'
if [[ -z "${skip_markdown}" ]]; then
files=$(echo "${changed_files}" | grep '^lib/node_modules/@stdlib/.*/README\.md$' | tr '\n' ' ')
if [[ -n "${files}" ]]; then
make FILES="${files}" lint-markdown-package-readme-files > /dev/null >&2
if [[ "$?" -ne 0 ]]; then
task_status 'failed'
echo '' >&2
echo 'Markdown lint errors for package README files.' >&2
return 1
fi
task_status 'passed'
else
task_status 'na'
fi
else
task_status 'skipped'
fi
# Lint docs Markdown files...
add_task 'lint_markdown_docs'
if [[ -z "${skip_markdown}" ]]; then
files=$(echo "${changed_files}" | grep '\.md$' | grep '^docs/' | tr '\n' ' ')
if [[ -n "${files}" ]]; then
make FILES="${files}" lint-markdown-docs-files > /dev/null >&2
if [[ "$?" -ne 0 ]]; then
task_status 'failed'
echo '' >&2
echo 'Markdown lint errors for docs files.' >&2
return 1
fi
task_status 'passed'
else
task_status 'na'
fi
else
task_status 'skipped'
fi
# Lint other Markdown files...
add_task 'lint_markdown'
if [[ -z "${skip_markdown}" ]]; then
files=$(echo "${changed_files}" | grep '\.md$' | grep -v '\.github/workflows/.*\.md$' | tr '\n' ' ')
files=$(echo "${changed_files}" | grep '\.md$' | grep -v '\.github/workflows/.*\.md$' | grep -v 'lib/node_modules/@stdlib/.*/README\.md$' | grep -v '^docs/' | tr '\n' ' ')
if [[ -n "${files}" ]]; then
make FILES="${files}" lint-markdown-files > /dev/null >&2
if [[ "$?" -ne 0 ]]; then
Expand Down
118 changes: 118 additions & 0 deletions tools/make/lib/lint/markdown/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,121 @@ else
endif

.PHONY: lint-markdown-files

#/
# Lints package README Markdown files.
#
# @param {string} [MARKDOWN_PKG_READMES_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`)
# @param {*} [FAST_FAIL] - flag indicating whether to stop linting upon encountering a lint error
#
# @example
# make lint-markdown-package-readmes
#
# @example
# make lint-markdown-package-readmes MARKDOWN_PKG_READMES_FILTER=".*/math/base/special/abs/.*"
#/
lint-markdown-package-readmes: $(NODE_MODULES)
ifeq ($(FAIL_FAST), true)
$(QUIET) $(FIND_MARKDOWN_PKG_READMES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_PKG_READMES_FLAGS) $$file || exit 1; \
done
else
$(QUIET) $(FIND_MARKDOWN_PKG_READMES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_PKG_READMES_FLAGS) $$file || echo 'Linting failed.'; \
done
endif

.PHONY: lint-markdown-package-readmes

#/
# Lints a specified list of package README Markdown files.
#
# ## Notes
#
# - This rule is useful when wanting to lint a list of package README files generated by some other command (e.g., a list of changed package README files obtained via `git diff`).
#
# @param {string} FILES - list of package README file paths
# @param {*} [FAST_FAIL] - flag indicating whether to stop linting upon encountering a lint error
#
# @example
# make lint-markdown-package-readme-files FILES='/foo/README.md /bar/README.md'
#/
lint-markdown-package-readme-files: $(NODE_MODULES)
ifeq ($(FAIL_FAST), true)
$(QUIET) for file in $(FILES); do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_PKG_READMES_FLAGS) $$file || exit 1; \
done
else
$(QUIET) for file in $(FILES); do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_PKG_READMES_FLAGS) $$file || echo 'Linting failed.'; \
done
endif

.PHONY: lint-markdown-package-readme-files

#/
# Lints documentation Markdown files.
#
# @param {string} [MARKDOWN_DOCS_FILTER] - file path pattern (e.g., `.*/contributing/.*`)
# @param {*} [FAST_FAIL] - flag indicating whether to stop linting upon encountering a lint error
#
# @example
# make lint-markdown-docs
#
# @example
# make lint-markdown-docs MARKDOWN_DOCS_FILTER=".*/contributing/.*"
#/
lint-markdown-docs: $(NODE_MODULES)
ifeq ($(FAIL_FAST), true)
$(QUIET) $(FIND_MARKDOWN_DOCS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_DOCS_FLAGS) $$file || exit 1; \
done
else
$(QUIET) $(FIND_MARKDOWN_DOCS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_DOCS_FLAGS) $$file || echo 'Linting failed.'; \
done
endif

.PHONY: lint-markdown-docs

#/
# Lints a specified list of documentation Markdown files.
#
# ## Notes
#
# - This rule is useful when wanting to lint a list of documentation Markdown files generated by some other command (e.g., a list of changed documentation Markdown files obtained via `git diff`).
#
# @param {string} FILES - list of documentation Markdown file paths
# @param {*} [FAST_FAIL] - flag indicating whether to stop linting upon encountering a lint error
#
# @example
# make lint-markdown-docs-files FILES='/foo/bar.md /baz/qux.md'
#/
lint-markdown-docs-files: $(NODE_MODULES)
ifeq ($(FAIL_FAST), true)
$(QUIET) for file in $(FILES); do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_DOCS_FLAGS) $$file || exit 1; \
done
else
$(QUIET) for file in $(FILES); do \
echo ''; \
echo "Linting file: $$file"; \
NODE_PATH="$(NODE_PATH)" $(MARKDOWN_LINT) $(MARKDOWN_LINT_DOCS_FLAGS) $$file || echo 'Linting failed.'; \
done
endif

.PHONY: lint-markdown-docs-files
Loading