obsohtml 1.9.6 → 1.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +2 -2
- package/.github/dependabot.yml +1 -1
- package/README.md +4 -2
- package/bin/obsohtml.js +57 -19
- package/bin/obsohtml.test.js +70 -26
- package/package.json +2 -2
package/.github/FUNDING.yml
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
buy_me_a_coffee: meiert
|
|
1
|
+
# buy_me_a_coffee: meiert
|
|
2
2
|
github: j9t
|
|
3
|
-
liberapay: j9t
|
|
3
|
+
# liberapay: j9t
|
package/.github/dependabot.yml
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/obsohtml) [](https://github.com/j9t/obsohtml/actions) [](https://socket.dev/npm/package/obsohtml)
|
|
4
4
|
|
|
5
|
-
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
|
|
5
|
+
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.
|
|
6
6
|
|
|
7
7
|
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).
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ npm i obsohtml
|
|
|
20
20
|
|
|
21
21
|
#### Execution
|
|
22
22
|
|
|
23
|
-
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
|
|
23
|
+
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.
|
|
24
24
|
|
|
25
25
|
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.
|
|
26
26
|
|
|
@@ -80,6 +80,8 @@ node bin/obsohtml.js -f ../path/to/folder
|
|
|
80
80
|
|
|
81
81
|
The script will output messages to the console indicating any obsolete attributes or elements found in the scanned files, along with the file paths where they were detected.
|
|
82
82
|
|
|
83
|
+
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.
|
|
84
|
+
|
|
83
85
|
## Background
|
|
84
86
|
|
|
85
87
|
This started as an experiment, in which I used AI to produce this little HTML quality helper, its tests, and its documentation. While it’s pretty straightforward, I’m sure to have missed something. Please [file an issue](https://github.com/j9t/obsohtml/issues) or contact me directly if you spot a problem or have a suggestion.
|
package/bin/obsohtml.js
CHANGED
|
@@ -18,35 +18,54 @@ const obsoleteAttributes = [
|
|
|
18
18
|
'align', 'background', 'bgcolor', 'border', 'frameborder', 'hspace', 'marginheight', 'marginwidth', 'noshade', 'nowrap', 'scrolling', 'valign', 'vspace'
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
+
// Pre-compile regexes once at startup
|
|
22
|
+
const elementRegexes = obsoleteElements.map(element => ({
|
|
23
|
+
element,
|
|
24
|
+
regex: new RegExp(`<\\s*${element}\\b`, 'i'),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const attributeRegexes = obsoleteAttributes.map(attribute => ({
|
|
28
|
+
attribute,
|
|
29
|
+
// Matches the attribute preceded by whitespace anywhere in a tag, without
|
|
30
|
+
// requiring it to be the last attribute before the closing bracket.
|
|
31
|
+
regex: new RegExp(`<[^>]*\\s${attribute}\\b(\\s*=\\s*(?:"[^"]*"|'[^']*'|[^"'\\s>]+))?`, 'i'),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Directories to skip during traversal
|
|
35
|
+
const EXCLUDED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'vendor']);
|
|
36
|
+
|
|
21
37
|
// Default project directory (user’s home directory)
|
|
22
38
|
const defaultProjectDirectory = os.homedir();
|
|
23
39
|
|
|
40
|
+
// Track whether any obsolete HTML was found
|
|
41
|
+
let foundObsolete = false;
|
|
42
|
+
|
|
24
43
|
// Function to find obsolete elements and attributes in a file
|
|
25
|
-
|
|
44
|
+
function findObsolete(filePath) {
|
|
26
45
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
27
46
|
|
|
28
47
|
// Check for obsolete elements
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
48
|
+
for (const { element, regex } of elementRegexes) {
|
|
49
|
+
if (regex.test(content)) {
|
|
50
|
+
foundObsolete = true;
|
|
32
51
|
const message = styleText('blue', `Found obsolete element ${styleText('bold', `'${element}'`)} in ${filePath}`);
|
|
33
52
|
console.log(message);
|
|
34
53
|
}
|
|
35
|
-
}
|
|
54
|
+
}
|
|
36
55
|
|
|
37
56
|
// Check for obsolete attributes
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
for (const { attribute, regex } of attributeRegexes) {
|
|
58
|
+
if (regex.test(content)) {
|
|
59
|
+
foundObsolete = true;
|
|
41
60
|
const message = styleText('green', `Found obsolete attribute ${styleText('bold', `'${attribute}'`)} in ${filePath}`);
|
|
42
61
|
console.log(message);
|
|
43
62
|
}
|
|
44
|
-
}
|
|
63
|
+
}
|
|
45
64
|
}
|
|
46
65
|
|
|
47
|
-
// Function to walk through the project directory, excluding
|
|
66
|
+
// Function to walk through the project directory, excluding common build/VCS directories
|
|
48
67
|
function walkDirectory(directory, verbose) {
|
|
49
|
-
const MAX_PATH_LENGTH = 255;
|
|
68
|
+
const MAX_PATH_LENGTH = 255;
|
|
50
69
|
let files;
|
|
51
70
|
|
|
52
71
|
try {
|
|
@@ -63,25 +82,31 @@ function walkDirectory(directory, verbose) {
|
|
|
63
82
|
}
|
|
64
83
|
}
|
|
65
84
|
|
|
66
|
-
|
|
85
|
+
for (const file of files) {
|
|
67
86
|
const fullPath = path.join(directory, file);
|
|
68
87
|
|
|
69
88
|
if (fullPath.length > MAX_PATH_LENGTH) {
|
|
70
89
|
if (verbose) console.warn(`Skipping file or directory with path too long: ${fullPath}`);
|
|
71
|
-
|
|
90
|
+
continue;
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
try {
|
|
75
94
|
const stats = fs.lstatSync(fullPath);
|
|
76
95
|
if (stats.isSymbolicLink()) {
|
|
77
96
|
if (verbose) console.warn(`Skipping symbolic link: ${fullPath}`);
|
|
78
|
-
|
|
97
|
+
continue;
|
|
79
98
|
}
|
|
80
99
|
if (stats.isDirectory()) {
|
|
81
|
-
if (file
|
|
100
|
+
if (!EXCLUDED_DIRS.has(file)) {
|
|
82
101
|
walkDirectory(fullPath, verbose);
|
|
83
102
|
}
|
|
84
|
-
} else if (
|
|
103
|
+
} else if (
|
|
104
|
+
fullPath.endsWith('.html') || fullPath.endsWith('.htm') ||
|
|
105
|
+
fullPath.endsWith('.php') ||
|
|
106
|
+
fullPath.endsWith('.njk') || fullPath.endsWith('.twig') ||
|
|
107
|
+
fullPath.endsWith('.js') || fullPath.endsWith('.jsx') ||
|
|
108
|
+
fullPath.endsWith('.ts') || fullPath.endsWith('.tsx')
|
|
109
|
+
) {
|
|
85
110
|
findObsolete(fullPath);
|
|
86
111
|
}
|
|
87
112
|
} catch (err) {
|
|
@@ -91,12 +116,25 @@ function walkDirectory(directory, verbose) {
|
|
|
91
116
|
throw err;
|
|
92
117
|
}
|
|
93
118
|
}
|
|
94
|
-
}
|
|
119
|
+
}
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
// Main function to execute the script
|
|
98
|
-
|
|
99
|
-
|
|
123
|
+
function main(projectDirectory = defaultProjectDirectory, verbose = false) {
|
|
124
|
+
let stats;
|
|
125
|
+
try {
|
|
126
|
+
stats = fs.lstatSync(projectDirectory);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (err.code !== 'ENOENT') throw err;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (stats?.isFile()) {
|
|
132
|
+
findObsolete(projectDirectory);
|
|
133
|
+
} else {
|
|
134
|
+
walkDirectory(projectDirectory, verbose);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (foundObsolete) process.exit(1);
|
|
100
138
|
}
|
|
101
139
|
|
|
102
140
|
// Define command line options
|
package/bin/obsohtml.test.js
CHANGED
|
@@ -9,71 +9,115 @@ import { stripVTControlCharacters } from 'node:util';
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const scriptPath = path.join(__dirname, 'obsohtml.js');
|
|
11
11
|
|
|
12
|
+
function run(args) {
|
|
13
|
+
const result = spawnSync('node', [scriptPath, ...args], { encoding: 'utf-8' });
|
|
14
|
+
return {
|
|
15
|
+
stdout: stripVTControlCharacters(result.stdout),
|
|
16
|
+
stderr: stripVTControlCharacters(result.stderr),
|
|
17
|
+
status: result.status,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
describe('ObsoHTML', () => {
|
|
13
22
|
const tempDir = path.join(__dirname, 'temp_test_dir');
|
|
14
23
|
const tempFile = path.join(tempDir, 'test.html');
|
|
15
24
|
const tempFileWithAttributes = path.join(tempDir, 'test_with_attributes.html');
|
|
25
|
+
const tempFileWithMidTagAttribute = path.join(tempDir, 'test_mid_tag_attribute.html');
|
|
16
26
|
const tempFileWithMinimizedAttributes = path.join(tempDir, 'test_with_minimized_attributes.html');
|
|
17
27
|
const tempTwigFile = path.join(tempDir, 'test.twig');
|
|
28
|
+
const tempJsxFile = path.join(tempDir, 'test.jsx');
|
|
29
|
+
const tempTsxFile = path.join(tempDir, 'test.tsx');
|
|
18
30
|
|
|
19
31
|
before(() => {
|
|
20
|
-
// Create a temporary directory and files
|
|
21
32
|
if (!fs.existsSync(tempDir)) {
|
|
22
33
|
fs.mkdirSync(tempDir);
|
|
23
34
|
}
|
|
24
35
|
fs.writeFileSync(tempFile, '<!DOCTYPE html><html><title>Test</title><body><center>Test</center></body></html>');
|
|
25
36
|
fs.writeFileSync(tempFileWithAttributes, '<!DOCTYPE html><html><title>Test</title><body><img src=test.jpg alt=Test align=left></body></html>');
|
|
37
|
+
fs.writeFileSync(tempFileWithMidTagAttribute, '<!DOCTYPE html><html><title>Test</title><body><img align=left src=test.jpg></body></html>');
|
|
26
38
|
fs.writeFileSync(tempFileWithMinimizedAttributes, '<!DOCTYPE html><html><title>Test</title><hr noshade><table><tr><th class=nowrap></table>');
|
|
27
39
|
fs.writeFileSync(tempTwigFile, '<!DOCTYPE html><html><title>Test</title><isindex>');
|
|
40
|
+
fs.writeFileSync(tempJsxFile, 'export default () => <center>Hello</center>;');
|
|
41
|
+
fs.writeFileSync(tempTsxFile, 'export default (): JSX.Element => <marquee>Hello</marquee>;');
|
|
28
42
|
});
|
|
29
43
|
|
|
30
44
|
after(() => {
|
|
31
|
-
// Clean up the temporary directory and files
|
|
32
45
|
fs.unlinkSync(tempFile);
|
|
33
46
|
fs.unlinkSync(tempFileWithAttributes);
|
|
47
|
+
fs.unlinkSync(tempFileWithMidTagAttribute);
|
|
34
48
|
fs.unlinkSync(tempFileWithMinimizedAttributes);
|
|
35
49
|
fs.unlinkSync(tempTwigFile);
|
|
50
|
+
fs.unlinkSync(tempJsxFile);
|
|
51
|
+
fs.unlinkSync(tempTsxFile);
|
|
36
52
|
fs.rmdirSync(tempDir);
|
|
37
53
|
});
|
|
38
54
|
|
|
39
55
|
test('Detect obsolete elements', () => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
assert.ok(output.includes("Found obsolete element 'center'"));
|
|
56
|
+
const { stdout } = run(['-f', tempDir]);
|
|
57
|
+
assert.ok(stdout.includes("Found obsolete element 'center'"));
|
|
43
58
|
});
|
|
44
59
|
|
|
45
60
|
test('Detect obsolete attributes', () => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
assert.ok(output.includes("Found obsolete attribute 'align'"));
|
|
61
|
+
const { stdout } = run(['-f', tempDir]);
|
|
62
|
+
assert.ok(stdout.includes("Found obsolete attribute 'align'"));
|
|
49
63
|
});
|
|
50
64
|
|
|
51
65
|
test('Detect obsolete elements and attributes using absolute path', () => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
assert.ok(output.includes("Found obsolete element 'center'"));
|
|
56
|
-
assert.ok(output.includes("Found obsolete attribute 'align'"));
|
|
66
|
+
const { stdout } = run(['-f', path.resolve(tempDir)]);
|
|
67
|
+
assert.ok(stdout.includes("Found obsolete element 'center'"));
|
|
68
|
+
assert.ok(stdout.includes("Found obsolete attribute 'align'"));
|
|
57
69
|
});
|
|
58
70
|
|
|
59
71
|
test('Detect obsolete elements and attributes using relative path', () => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
assert.ok(output.includes("Found obsolete element 'center'"));
|
|
64
|
-
assert.ok(output.includes("Found obsolete attribute 'align'"));
|
|
72
|
+
const { stdout } = run(['--folder', path.relative(process.cwd(), tempDir)]);
|
|
73
|
+
assert.ok(stdout.includes("Found obsolete element 'center'"));
|
|
74
|
+
assert.ok(stdout.includes("Found obsolete attribute 'align'"));
|
|
65
75
|
});
|
|
66
76
|
|
|
67
77
|
test('Detect obsolete minimized attributes', () => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
assert.ok(
|
|
71
|
-
assert.ok(!output.includes("Found obsolete attribute 'nowrap'"));
|
|
78
|
+
const { stdout } = run(['-f', tempDir]);
|
|
79
|
+
assert.ok(stdout.includes("Found obsolete attribute 'noshade'"));
|
|
80
|
+
assert.ok(!stdout.includes("Found obsolete attribute 'nowrap'"));
|
|
72
81
|
});
|
|
73
82
|
|
|
74
83
|
test('Detect obsolete elements in Twig file', () => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
const { stdout } = run(['-f', tempDir]);
|
|
85
|
+
assert.ok(stdout.includes("Found obsolete element 'isindex'"));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('Detect obsolete attribute when it is not the last attribute in a tag', () => {
|
|
89
|
+
const { stdout } = run(['-f', tempFileWithMidTagAttribute]);
|
|
90
|
+
assert.ok(stdout.includes("Found obsolete attribute 'align'"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Detect obsolete elements in JSX file', () => {
|
|
94
|
+
const { stdout } = run(['-f', tempJsxFile]);
|
|
95
|
+
assert.ok(stdout.includes("Found obsolete element 'center'"));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('Detect obsolete elements in TSX file', () => {
|
|
99
|
+
const { stdout } = run(['-f', tempTsxFile]);
|
|
100
|
+
assert.ok(stdout.includes("Found obsolete element 'marquee'"));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('Exit with code 1 when obsolete HTML is found', () => {
|
|
104
|
+
const { status } = run(['-f', tempDir]);
|
|
105
|
+
assert.strictEqual(status, 1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('Exit with code 0 when no obsolete HTML is found', () => {
|
|
109
|
+
const cleanFile = path.join(tempDir, 'clean.html');
|
|
110
|
+
fs.writeFileSync(cleanFile, '<!DOCTYPE html><html><title>Clean</title><body><p>No issues here.</p></body></html>');
|
|
111
|
+
try {
|
|
112
|
+
const { status } = run(['-f', cleanFile]);
|
|
113
|
+
assert.strictEqual(status, 0);
|
|
114
|
+
} finally {
|
|
115
|
+
fs.unlinkSync(cleanFile);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('Verbose mode reports skipped non-existent directory', () => {
|
|
120
|
+
const { stderr } = run(['-f', path.join(tempDir, 'nonexistent'), '-v']);
|
|
121
|
+
assert.ok(stderr.includes('Skipping non-existent directory'));
|
|
78
122
|
});
|
|
79
|
-
});
|
|
123
|
+
});
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"qc",
|
|
16
16
|
"semantics"
|
|
17
17
|
],
|
|
18
|
-
"license": "
|
|
18
|
+
"license": "MIT",
|
|
19
19
|
"name": "obsohtml",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
"test": "node --test bin/obsohtml.test.js"
|
|
27
27
|
},
|
|
28
28
|
"type": "module",
|
|
29
|
-
"version": "1.9.
|
|
29
|
+
"version": "1.9.7"
|
|
30
30
|
}
|