@@ -1,2 +0,2 @@ | ||
| # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference | ||
| # https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference | ||
@@ -3,0 +3,0 @@ version: 2 |
@@ -1,3 +0,3 @@ | ||
| buy_me_a_coffee: meiert | ||
| # buy_me_a_coffee: meiert | ||
| github: j9t | ||
| liberapay: j9t | ||
| # liberapay: j9t |
+57
-19
@@ -21,31 +21,50 @@ #!/usr/bin/env node | ||
| // Pre-compile regexes once at startup | ||
| const elementRegexes = obsoleteElements.map(element => ({ | ||
| element, | ||
| regex: new RegExp(`<\\s*${element}\\b`, 'i'), | ||
| })); | ||
| const attributeRegexes = obsoleteAttributes.map(attribute => ({ | ||
| attribute, | ||
| // Matches the attribute preceded by whitespace anywhere in a tag, without | ||
| // requiring it to be the last attribute before the closing bracket. | ||
| regex: new RegExp(`<[^>]*\\s${attribute}\\b(\\s*=\\s*(?:"[^"]*"|'[^']*'|[^"'\\s>]+))?`, 'i'), | ||
| })); | ||
| // Directories to skip during traversal | ||
| const EXCLUDED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'vendor']); | ||
| // Default project directory (user’s home directory) | ||
| const defaultProjectDirectory = os.homedir(); | ||
| // Track whether any obsolete HTML was found | ||
| let foundObsolete = false; | ||
| // Function to find obsolete elements and attributes in a file | ||
| async function findObsolete(filePath) { | ||
| function findObsolete(filePath) { | ||
| const content = fs.readFileSync(filePath, 'utf8'); | ||
| // Check for obsolete elements | ||
| obsoleteElements.forEach(element => { | ||
| const elementRegex = new RegExp(`<\\s*${element}\\b`, 'i'); | ||
| if (elementRegex.test(content)) { | ||
| for (const { element, regex } of elementRegexes) { | ||
| if (regex.test(content)) { | ||
| foundObsolete = true; | ||
| const message = styleText('blue', `Found obsolete element ${styleText('bold', `'${element}'`)} in ${filePath}`); | ||
| console.log(message); | ||
| } | ||
| }); | ||
| } | ||
| // Check for obsolete attributes | ||
| obsoleteAttributes.forEach(attribute => { | ||
| const attributeRegex = new RegExp(`<[^>]*\\s${attribute}\\b(\\s*=\\s*(?:"[^"]*"|'[^']*'|[^"'\\s>]+))?\\s*(?=/?>)`, 'i'); | ||
| if (attributeRegex.test(content)) { | ||
| for (const { attribute, regex } of attributeRegexes) { | ||
| if (regex.test(content)) { | ||
| foundObsolete = true; | ||
| const message = styleText('green', `Found obsolete attribute ${styleText('bold', `'${attribute}'`)} in ${filePath}`); | ||
| console.log(message); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| // Function to walk through the project directory, excluding node_modules directories | ||
| // Function to walk through the project directory, excluding common build/VCS directories | ||
| function walkDirectory(directory, verbose) { | ||
| const MAX_PATH_LENGTH = 255; // Adjust this value based on your OS limits | ||
| const MAX_PATH_LENGTH = 255; | ||
| let files; | ||
@@ -67,3 +86,3 @@ | ||
| files.forEach(file => { | ||
| for (const file of files) { | ||
| const fullPath = path.join(directory, file); | ||
@@ -73,3 +92,3 @@ | ||
| if (verbose) console.warn(`Skipping file or directory with path too long: ${fullPath}`); | ||
| return; | ||
| continue; | ||
| } | ||
@@ -81,9 +100,15 @@ | ||
| if (verbose) console.warn(`Skipping symbolic link: ${fullPath}`); | ||
| return; | ||
| continue; | ||
| } | ||
| if (stats.isDirectory()) { | ||
| if (file !== 'node_modules') { | ||
| if (!EXCLUDED_DIRS.has(file)) { | ||
| walkDirectory(fullPath, verbose); | ||
| } | ||
| } else if (fullPath.endsWith('.html') || fullPath.endsWith('.htm') || fullPath.endsWith('.php') || fullPath.endsWith('.njk') || fullPath.endsWith('.twig') || fullPath.endsWith('.js') || fullPath.endsWith('.ts')) { | ||
| } else if ( | ||
| fullPath.endsWith('.html') || fullPath.endsWith('.htm') || | ||
| fullPath.endsWith('.php') || | ||
| fullPath.endsWith('.njk') || fullPath.endsWith('.twig') || | ||
| fullPath.endsWith('.js') || fullPath.endsWith('.jsx') || | ||
| fullPath.endsWith('.ts') || fullPath.endsWith('.tsx') | ||
| ) { | ||
| findObsolete(fullPath); | ||
@@ -98,8 +123,21 @@ } | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| // Main function to execute the script | ||
| async function main(projectDirectory = defaultProjectDirectory, verbose = false) { | ||
| await walkDirectory(projectDirectory, verbose); | ||
| function main(projectDirectory = defaultProjectDirectory, verbose = false) { | ||
| let stats; | ||
| try { | ||
| stats = fs.lstatSync(projectDirectory); | ||
| } catch (err) { | ||
| if (err.code !== 'ENOENT') throw err; | ||
| } | ||
| if (stats?.isFile()) { | ||
| findObsolete(projectDirectory); | ||
| } else { | ||
| walkDirectory(projectDirectory, verbose); | ||
| } | ||
| if (foundObsolete) process.exit(1); | ||
| } | ||
@@ -106,0 +144,0 @@ |
+70
-26
@@ -12,2 +12,11 @@ import fs from 'node:fs'; | ||
| function run(args) { | ||
| const result = spawnSync('node', [scriptPath, ...args], { encoding: 'utf-8' }); | ||
| return { | ||
| stdout: stripVTControlCharacters(result.stdout), | ||
| stderr: stripVTControlCharacters(result.stderr), | ||
| status: result.status, | ||
| }; | ||
| } | ||
| describe('ObsoHTML', () => { | ||
@@ -17,7 +26,9 @@ const tempDir = path.join(__dirname, 'temp_test_dir'); | ||
| const tempFileWithAttributes = path.join(tempDir, 'test_with_attributes.html'); | ||
| const tempFileWithMidTagAttribute = path.join(tempDir, 'test_mid_tag_attribute.html'); | ||
| const tempFileWithMinimizedAttributes = path.join(tempDir, 'test_with_minimized_attributes.html'); | ||
| const tempTwigFile = path.join(tempDir, 'test.twig'); | ||
| const tempJsxFile = path.join(tempDir, 'test.jsx'); | ||
| const tempTsxFile = path.join(tempDir, 'test.tsx'); | ||
| before(() => { | ||
| // Create a temporary directory and files | ||
| if (!fs.existsSync(tempDir)) { | ||
@@ -28,12 +39,17 @@ fs.mkdirSync(tempDir); | ||
| fs.writeFileSync(tempFileWithAttributes, '<!DOCTYPE html><html><title>Test</title><body><img src=test.jpg alt=Test align=left></body></html>'); | ||
| fs.writeFileSync(tempFileWithMidTagAttribute, '<!DOCTYPE html><html><title>Test</title><body><img align=left src=test.jpg></body></html>'); | ||
| fs.writeFileSync(tempFileWithMinimizedAttributes, '<!DOCTYPE html><html><title>Test</title><hr noshade><table><tr><th class=nowrap></table>'); | ||
| fs.writeFileSync(tempTwigFile, '<!DOCTYPE html><html><title>Test</title><isindex>'); | ||
| fs.writeFileSync(tempJsxFile, 'export default () => <center>Hello</center>;'); | ||
| fs.writeFileSync(tempTsxFile, 'export default (): JSX.Element => <marquee>Hello</marquee>;'); | ||
| }); | ||
| after(() => { | ||
| // Clean up the temporary directory and files | ||
| fs.unlinkSync(tempFile); | ||
| fs.unlinkSync(tempFileWithAttributes); | ||
| fs.unlinkSync(tempFileWithMidTagAttribute); | ||
| fs.unlinkSync(tempFileWithMinimizedAttributes); | ||
| fs.unlinkSync(tempTwigFile); | ||
| fs.unlinkSync(tempJsxFile); | ||
| fs.unlinkSync(tempTsxFile); | ||
| fs.rmdirSync(tempDir); | ||
@@ -43,41 +59,69 @@ }); | ||
| test('Detect obsolete elements', () => { | ||
| const result = spawnSync('node', [scriptPath, '-f', tempDir], { encoding: 'utf-8' }); | ||
| const output = stripVTControlCharacters(result.stdout); | ||
| assert.ok(output.includes("Found obsolete element 'center'")); | ||
| const { stdout } = run(['-f', tempDir]); | ||
| assert.ok(stdout.includes("Found obsolete element 'center'")); | ||
| }); | ||
| test('Detect obsolete attributes', () => { | ||
| const result = spawnSync('node', [scriptPath, '-f', tempDir], { encoding: 'utf-8' }); | ||
| const output = stripVTControlCharacters(result.stdout); | ||
| assert.ok(output.includes("Found obsolete attribute 'align'")); | ||
| const { stdout } = run(['-f', tempDir]); | ||
| assert.ok(stdout.includes("Found obsolete attribute 'align'")); | ||
| }); | ||
| test('Detect obsolete elements and attributes using absolute path', () => { | ||
| const absolutePath = path.resolve(tempDir); | ||
| const result = spawnSync('node', [scriptPath, '-f', absolutePath], { encoding: 'utf-8' }); | ||
| const output = stripVTControlCharacters(result.stdout); | ||
| assert.ok(output.includes("Found obsolete element 'center'")); | ||
| assert.ok(output.includes("Found obsolete attribute 'align'")); | ||
| const { stdout } = run(['-f', path.resolve(tempDir)]); | ||
| assert.ok(stdout.includes("Found obsolete element 'center'")); | ||
| assert.ok(stdout.includes("Found obsolete attribute 'align'")); | ||
| }); | ||
| test('Detect obsolete elements and attributes using relative path', () => { | ||
| const relativePath = path.relative(process.cwd(), tempDir); | ||
| const result = spawnSync('node', [scriptPath, '--folder', relativePath], { encoding: 'utf-8' }); | ||
| const output = stripVTControlCharacters(result.stdout); | ||
| assert.ok(output.includes("Found obsolete element 'center'")); | ||
| assert.ok(output.includes("Found obsolete attribute 'align'")); | ||
| const { stdout } = run(['--folder', path.relative(process.cwd(), tempDir)]); | ||
| assert.ok(stdout.includes("Found obsolete element 'center'")); | ||
| assert.ok(stdout.includes("Found obsolete attribute 'align'")); | ||
| }); | ||
| test('Detect obsolete minimized attributes', () => { | ||
| const result = spawnSync('node', [scriptPath, '-f', tempDir], { encoding: 'utf-8' }); | ||
| const output = stripVTControlCharacters(result.stdout); | ||
| assert.ok(output.includes("Found obsolete attribute 'noshade'")); | ||
| assert.ok(!output.includes("Found obsolete attribute 'nowrap'")); | ||
| const { stdout } = run(['-f', tempDir]); | ||
| assert.ok(stdout.includes("Found obsolete attribute 'noshade'")); | ||
| assert.ok(!stdout.includes("Found obsolete attribute 'nowrap'")); | ||
| }); | ||
| test('Detect obsolete elements in Twig file', () => { | ||
| const result = spawnSync('node', [scriptPath, '-f', tempDir], { encoding: 'utf-8' }); | ||
| const output = stripVTControlCharacters(result.stdout); | ||
| assert.ok(output.includes("Found obsolete element 'isindex'")); | ||
| const { stdout } = run(['-f', tempDir]); | ||
| assert.ok(stdout.includes("Found obsolete element 'isindex'")); | ||
| }); | ||
| }); | ||
| test('Detect obsolete attribute when it is not the last attribute in a tag', () => { | ||
| const { stdout } = run(['-f', tempFileWithMidTagAttribute]); | ||
| assert.ok(stdout.includes("Found obsolete attribute 'align'")); | ||
| }); | ||
| test('Detect obsolete elements in JSX file', () => { | ||
| const { stdout } = run(['-f', tempJsxFile]); | ||
| assert.ok(stdout.includes("Found obsolete element 'center'")); | ||
| }); | ||
| test('Detect obsolete elements in TSX file', () => { | ||
| const { stdout } = run(['-f', tempTsxFile]); | ||
| assert.ok(stdout.includes("Found obsolete element 'marquee'")); | ||
| }); | ||
| test('Exit with code 1 when obsolete HTML is found', () => { | ||
| const { status } = run(['-f', tempDir]); | ||
| assert.strictEqual(status, 1); | ||
| }); | ||
| test('Exit with code 0 when no obsolete HTML is found', () => { | ||
| const cleanFile = path.join(tempDir, 'clean.html'); | ||
| fs.writeFileSync(cleanFile, '<!DOCTYPE html><html><title>Clean</title><body><p>No issues here.</p></body></html>'); | ||
| try { | ||
| const { status } = run(['-f', cleanFile]); | ||
| assert.strictEqual(status, 0); | ||
| } finally { | ||
| fs.unlinkSync(cleanFile); | ||
| } | ||
| }); | ||
| test('Verbose mode reports skipped non-existent directory', () => { | ||
| const { stderr } = run(['-f', path.join(tempDir, 'nonexistent'), '-v']); | ||
| assert.ok(stderr.includes('Skipping non-existent directory')); | ||
| }); | ||
| }); |
+2
-2
@@ -18,3 +18,3 @@ { | ||
| ], | ||
| "license": "CC-BY-SA-4.0", | ||
| "license": "MIT", | ||
| "name": "obsohtml", | ||
@@ -30,3 +30,3 @@ "repository": { | ||
| "type": "module", | ||
| "version": "1.9.6" | ||
| "version": "1.9.7" | ||
| } |
+4
-2
@@ -5,3 +5,3 @@ # ObsoHTML, the Obsolete HTML Checker | ||
| ObsoHTML is a Node.js script designed to scan HTML, PHP, Nunjucks, Twig, JavaScript, and TypeScript files for obsolete or proprietary HTML attributes and elements (in scripts, it would catch JSX syntax). It helps you identify and update deprecated HTML code to be more sure to use web standards. | ||
| ObsoHTML is a Node.js script designed to scan HTML, PHP, Nunjucks, Twig, JavaScript, and TypeScript files for obsolete or proprietary HTML attributes and elements. It helps you identify and update deprecated HTML code to be more sure to use web standards. | ||
@@ -24,3 +24,3 @@ ObsoHTML has inherent limitations and may not find all obsolete attributes and elements. If you run into a problem, please [file an issue](https://github.com/j9t/obsohtml/issues). | ||
| The script accepts a folder path as a command line option, which can be specified in both short form (`-f`) and long form (`--folder`). The folder path can be either absolute or relative. | ||
| The script accepts a folder or file path as a command line option, which can be specified in both short form (`-f`) and long form (`--folder`). The path can be either absolute or relative. | ||
@@ -85,2 +85,4 @@ The script can be run in “verbose” mode by appending `-v` or `--verbose` to the command. This will show information about files and directories that were skipped. | ||
| The script exits with code `1` if any obsolete HTML is found, and `0` if none is found, making it suitable for use in CI pipelines. | ||
| ## Background | ||
@@ -87,0 +89,0 @@ |
Copyleft License
LicenseCopyleft license information was found.
Found 1 instance in 1 package
Mixed license
LicensePackage contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
LicenseA license not known to be considered permissive was found.
Found 1 instance in 1 package
17708
15.56%0
-100%100
42.86%233
41.21%91
2.25%