Skip to content

Add PL HTML linter with reusable GitHub Actions workflow#2

Draft
Copilot wants to merge 7 commits intomainfrom
copilot/fix-f0caeb86-6653-4f19-86d3-102d59e54c74
Draft

Add PL HTML linter with reusable GitHub Actions workflow#2
Copilot wants to merge 7 commits intomainfrom
copilot/fix-f0caeb86-6653-4f19-86d3-102d59e54c74

Conversation

Copy link
Copy Markdown

Copilot AI commented Oct 3, 2025

This PR implements a comprehensive linter for PrairieLearn HTML files that runs automatically via GitHub Actions as a reusable workflow that can be called from other repositories.

Overview

The linter validates HTML files for proper XML syntax, ensuring that all tags are properly formatted, elements are correctly nested, and attributes have valid syntax. It also enforces PrairieLearn-specific rules to catch common errors early in the development process.

What's Included

Reusable GitHub Actions Workflow

  • Reusable workflow (.github/workflows/lint-html.yml) that can be called from other repositories
  • Test mode for validating the linter itself with expected failures
  • Zero setup required - uses Python 3.x standard library only
  • Fast execution - no external dependencies to install

Using in Other Repositories

To use the linter in your own repository, create a workflow file (e.g., .github/workflows/lint-html.yml):

name: Lint HTML Files

'on':
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  lint:
    uses: ucsb-cs/pl-linter/.github/workflows/lint-html.yml@main

Test Mode (This Repository)

This repository includes a special test mode that validates the linter correctly identifies both valid and invalid files:

  • Files listed in expected-failures must fail linting (to verify error detection)
  • All other files must pass linting
  • The workflow succeeds only if both conditions are met

The caller workflow (.github/workflows/test-linter.yml) demonstrates this:

jobs:
  test-lint:
    uses: ./.github/workflows/lint-html.yml
    with:
      test-mode: true
      expected-failures: 'example_invalid.html,example_pl_invalid.html'

Python Linter (lint_html.py)

The linter script:

  • Recursively finds all .html and .HTML files in the repository
  • Validates XML syntax using Python's built-in ElementTree parser
  • Enforces PrairieLearn-specific rules (see below)
  • Reports errors with clear messages including line numbers
  • Supports test mode for validation testing
  • Returns appropriate exit codes (0 for success, 1 for failures)

Example output (Normal Mode):

Scanning for HTML files in: /home/runner/work/pl-linter/pl-linter
Found 1 HTML file(s) to lint:
  - /home/runner/work/pl-linter/pl-linter/example.html

Linting: /home/runner/work/pl-linter/pl-linter/example.html
  ✓ PASSED

✓ All files passed linting!

Example output (Test Mode):

🧪 TEST MODE: Expecting these files to fail: example_invalid.html, example_pl_invalid.html

============================================================
TEST MODE VALIDATION
============================================================
✓ example.html: Correctly detected as VALID
✓ example_invalid.html: Correctly detected as INVALID
✓ example_pl_invalid.html: Correctly detected as INVALID
✓ example_pl_valid.html: Correctly detected as VALID
============================================================
✓ Test mode: All validations passed!

Error detection example:

Linting: example_invalid.html
  ❌ FAILED with 1 error(s):
    - XML syntax error: mismatched tag: line 11, column 4

Linting: example_pl_invalid.html
  ❌ FAILED with 1 error(s):
    - <pl-multiple-choice> element must not be nested inside other elements. It must be the root element of the document.

PrairieLearn-Specific Rules

The linter enforces the following PL-specific rules:

  • <pl-multiple-choice> nesting rule: If a <pl-multiple-choice> element exists in the file, it must be the root element of the document (not nested inside any other element)

Extensible Framework

The linter includes a check_custom_rules() function that makes it easy to add additional PrairieLearn-specific validation rules beyond basic XML syntax. The README provides clear examples of how to extend the linter with custom rules.

Example Files

The repository includes example HTML files to demonstrate the linter's functionality:

  • example.html - A valid HTML file that passes all checks
  • example_invalid.html - An invalid HTML file with mismatched tags (XML syntax error)
  • example_pl_valid.html - A valid PrairieLearn file with <pl-multiple-choice> as root element
  • example_pl_invalid.html - An invalid PrairieLearn file with nested <pl-multiple-choice> element

Testing

The implementation has been thoroughly tested with:

  • Valid HTML files that pass linting
  • Invalid HTML files with various syntax errors (unclosed tags, mismatched elements)
  • Valid PrairieLearn files with proper <pl-multiple-choice> placement
  • Invalid PrairieLearn files with improperly nested <pl-multiple-choice> elements
  • Both .html and .HTML file extensions
  • Repositories with no HTML files (handles gracefully)
  • Test mode validation that ensures the linter correctly identifies valid and invalid files

Documentation

Comprehensive documentation is provided in README.md including:

  • Feature overview
  • How to use the reusable workflow in other repositories
  • PrairieLearn-specific rules documentation
  • Test mode explanation
  • Example files description
  • Local usage instructions (normal and test mode)
  • Exit codes
  • Examples for adding custom validation rules

Fixes #1

Original prompt

This section details on the original issue you should resolve

<issue_title>Linter for PL HTML files</issue_title>
<issue_description>Please create a Github Actions workflow that will, on:

  • workflow_dispatch (manual run)
  • push to main
  • pull request to main

Go through all content in the repo, and for each file ending in .HTML

  1. Lint it for general XML style syntax (but not specifically any variant of HTML). That is, just check that tags are properly formatted, elements are properly nested, and attributes have the correct syntax.
    2: Check this specific rule: that any elements are at the top level of the file, not nested inside any other elements.

You may use any tools or programming languages that will help accomplish the task, as long as they are open source.</issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #1

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits October 3, 2025 23:40
Co-authored-by: pconrad <1119017+pconrad@users.noreply.github.com>
Co-authored-by: pconrad <1119017+pconrad@users.noreply.github.com>
Co-authored-by: pconrad <1119017+pconrad@users.noreply.github.com>
Co-authored-by: pconrad <1119017+pconrad@users.noreply.github.com>
Copilot AI changed the title [WIP] Linter for PL HTML files Add PL HTML linter with GitHub Actions workflow Oct 3, 2025
Copilot AI requested a review from pconrad October 3, 2025 23:46
@pconrad
Copy link
Copy Markdown
Contributor

pconrad commented Oct 4, 2025

@copilot This is a good start.

Please add:

  • An example of an HTML file that does NOT pass basic linting, i.e. one with mismatched tags, for example,
  • The custom PL linting rule that I requested: namely, that if an element called exists in the file, it must NOT be nested inside another element; that is it must have NO parent (or it must be its own parent, however the DOM model defines top level elements.)

Co-authored-by: pconrad <1119017+pconrad@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented Oct 4, 2025

@copilot This is a good start.

Please add:
...

Done! Added both requested features:

  1. Invalid HTML example (example_invalid.html) - demonstrates mismatched tags that fail XML syntax validation
  2. pl-multiple-choice nesting rule - enforces that <pl-multiple-choice> elements must be the root element of the document, not nested inside any other element

The implementation includes:

  • example_invalid.html - shows unclosed <h1> tag
  • example_pl_valid.html - <pl-multiple-choice> as root element (passes)
  • example_pl_invalid.html - <pl-multiple-choice> nested in <div> (fails with error message)

Test results:

Linting: example_invalid.html
  ❌ FAILED with 1 error(s):
    - XML syntax error: mismatched tag: line 11, column 4

Linting: example_pl_invalid.html
  ❌ FAILED with 1 error(s):
    - <pl-multiple-choice> element must not be nested inside other elements. It must be the root element of the document.

Linting: example_pl_valid.html
  ✓ PASSED

Commit: bbfc9a9

@pconrad
Copy link
Copy Markdown
Contributor

pconrad commented Oct 4, 2025

@copilot This is great. Here are a few more things we'd like to add.

  1. We want this workflow to be a reusable workflow that can be called from other repos that contain PrairieLearn content.
    Can you refactor this to be a resuable workflow, and provide, in the README.md, an example of what the calling workflow would look like?
  2. We need to retain the example_invalid.html and example_pl_invalid.html files so that we can test the code. But when we run this workflow on this particular repo, if the workflow is working properly, it will fail. But we actually want the opposite of that when we run the workflow in THIS repo. That is, we need this to be a special case: we want it to catch the problems and report them correctly. Do you see the paradox here?

How can we structure the workflow so that:

  1. It "passes" in THIS repo only when example.html and pl_example_valid.html report no errors, but the two files that end in _invalid.html DO report errors?
  2. It then passes for any other calling workflow if and only if ALL .html files in the repo have no problems reported?

Co-authored-by: pconrad <1119017+pconrad@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented Oct 4, 2025

@copilot This is great. Here are a few more things we'd like to add.

  1. We want this workflow to be a reusable workflow that can be called from other repos that contain PrairieLearn content.
    ...

Implemented both requested features:

1. Reusable Workflow
The workflow is now callable from other repos. To use it, create this in your repo's .github/workflows/lint-html.yml:

name: Lint HTML Files

'on':
  workflow_dispatch:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  lint:
    uses: ucsb-cs/pl-linter/.github/workflows/lint-html.yml@main

2. Test Mode for This Repo
Created test-linter.yml that validates the linter itself:

  • Expects example_invalid.html and example_pl_invalid.html to fail
  • Expects example.html and example_pl_valid.html to pass
  • Workflow succeeds only if all files behave as expected

Test mode output shows validation results:

============================================================
TEST MODE VALIDATION
============================================================
✓ example.html: Correctly detected as VALID
✓ example_invalid.html: Correctly detected as INVALID
✓ example_pl_invalid.html: Correctly detected as INVALID
✓ example_pl_valid.html: Correctly detected as VALID
============================================================
✓ Test mode: All validations passed!

The paradox is resolved: this repo's workflow passes when invalid examples are correctly detected as invalid, while other repos' workflows pass when all files are valid.

Commit: 93c608e

Copilot AI changed the title Add PL HTML linter with GitHub Actions workflow Add PL HTML linter with reusable GitHub Actions workflow Oct 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linter for PL HTML files

2 participants