cfpathcheck
Advanced tools
Comparing version 10.0.2 to 10.1.0
@@ -28,3 +28,3 @@ #!/usr/bin/env node | ||
*/ | ||
const violations = check(file, 'json'); | ||
const violations = check(file); | ||
const output = formatter(violations, reporter); | ||
@@ -31,0 +31,0 @@ |
@@ -5,2 +5,32 @@ # Change Log | ||
## [10.1.0] - 2024-07-30 | ||
### Added | ||
- NodeJS v22 test run. | ||
- fixtures/test-script.cfm, for testing cfscript syntax imports. | ||
- rimraf package, to clean up the `dist` folder before building ts type files. | ||
- tsconfig.json: `"moduleResolution": "Node"` | ||
### Updated | ||
- Internal rewrite, splitting the monolithic cfpathcheck.js module into more focused, smaller modules. | ||
- The external API hasn't changed, however, so this is only a minor version increment. | ||
- package.json: engines.node: 18 -> 18.20.3 | ||
- @snyk/protect@1.1292.1 | ||
- @types/chai@4.3.16 | ||
- @types/mocha@10.0.7 | ||
- @types/node@22.0.0 | ||
- chai@5.1.1 | ||
- eslint-config-xo@0.45.0 | ||
- glob@10.4.5 | ||
- ls-engines@0.9.2 | ||
- mocha@10.7.0 | ||
- npm-run-all2@6.2.2 | ||
- nyc@17.0.0 | ||
- prettier@3.3.3 | ||
- typescript@5.5.4 | ||
- (volta) node@18.20.3 | ||
- (volta) npm@10.8.2 | ||
### Removed | ||
- Unnecessary `format` argument in the call to the `check(file)` function. | ||
## [10.0.2] - 2024-03-16 | ||
@@ -644,3 +674,4 @@ ### Updated | ||
[10.0.1]: https://github.com/timbeadle/cfpathcheck/compare/10.0.1...10.0.2 | ||
[10.1.0]: https://github.com/timbeadle/cfpathcheck/compare/10.0.2...10.1.0 | ||
[10.0.2]: https://github.com/timbeadle/cfpathcheck/compare/10.0.1...10.0.2 | ||
[10.0.1]: https://github.com/timbeadle/cfpathcheck/compare/10.0.0...10.0.1 | ||
@@ -647,0 +678,0 @@ [10.0.0]: https://github.com/timbeadle/cfpathcheck/compare/9.0.0...10.0.0 |
@@ -1,9 +0,11 @@ | ||
export function comparePrefixArrays(prefixArray1: Array<string>, prefixArray2: Array<string>, message: string, severity: string): Array<object>; | ||
export function readFile(filePath: string): string; | ||
export function checkFile(filePath: string): Array<object>; | ||
export function getFiles(filePath: string): Array<string>; | ||
export function check(filePath: string): Array<object>; | ||
export function formatter(violations: Array<object>, format: string): string | Array<object>; | ||
export function writeFile(output: string, outFile: string): void; | ||
export function writeOutput(output: string | Array<object>): void; | ||
import { check } from './core.js'; | ||
import { checkFile } from './core.js'; | ||
import { comparePrefixArrays } from './core.js'; | ||
import { formatMessageText } from './format.js'; | ||
import { formatter } from './format.js'; | ||
import { getFiles } from './file.js'; | ||
import { readFile } from './file.js'; | ||
import { writeFile } from './file.js'; | ||
import { writeOutput } from './format.js'; | ||
export { check, checkFile, comparePrefixArrays, formatMessageText, formatter, getFiles, readFile, writeFile, writeOutput }; | ||
//# sourceMappingURL=cfpathcheck.d.ts.map |
export function containsObject(targetObject: object, list: Array<object>): boolean; | ||
export function checkIsXMLFile(line: string): boolean; | ||
export function matchAll(matchString: string, regex: RegExp): RegExpExecArray | any[]; | ||
export function normalisePath(filePath: string): string; | ||
//# sourceMappingURL=utils.d.ts.map |
@@ -1,342 +0,7 @@ | ||
import { | ||
readFileSync, existsSync, readdirSync, writeFileSync, | ||
} from 'fs'; | ||
import path from 'path'; | ||
import { sync } from 'glob'; | ||
import checkstyleFormatter from 'checkstyle-formatter'; | ||
import chalk from 'chalk'; | ||
import logSymbols from 'log-symbols'; | ||
import { containsObject, checkIsXMLFile, matchAll } from './utils.js'; | ||
import { check, checkFile, comparePrefixArrays } from './core.js'; | ||
import { getFiles, readFile, writeFile } from './file.js'; | ||
import { formatMessageText, formatter, writeOutput } from './format.js'; | ||
/** | ||
* Compares two arrays of cfml taglib prefixes to see if there are any mismatches. | ||
* | ||
* @param {Array<string>} prefixArray1 - The first array of taglib prefixes. | ||
* @param {Array<string>} prefixArray2 - The second array of taglib prefixes. | ||
* @param {string} message - The message to display in case of a violation. | ||
* @param {string} severity - The violation severity. | ||
* | ||
* @returns {Array<object>} | ||
*/ | ||
export const comparePrefixArrays = (prefixArray1, prefixArray2, message, severity) => { | ||
const prefixedViolations = {}; | ||
const prefixManifest = {}; | ||
const violations = []; | ||
for (const value of prefixArray1) { | ||
if ( | ||
!Object.hasOwn(prefixedViolations, value.prefix) | ||
) { | ||
prefixedViolations[value.prefix] = []; | ||
} | ||
const formattedMessage = message.replace('{2}', value.prefix); | ||
for (const value2 of prefixArray2) { | ||
// Key not found | ||
if (value.prefix === value2.prefix) { | ||
prefixManifest[value.prefix] = true; | ||
} else if ( | ||
!Object.hasOwn(prefixManifest, value.prefix) | ||
|| !prefixManifest[value.prefix] | ||
) { | ||
// Don't override a true value with false - true is sticky | ||
prefixManifest[value.prefix] = false; | ||
} | ||
const messageObject = { | ||
line: value.line, | ||
column: value.column || 1, | ||
message: formattedMessage, | ||
severity, | ||
}; | ||
if (!containsObject(messageObject, prefixedViolations[value.prefix])) { | ||
prefixedViolations[value.prefix].push(messageObject); | ||
} | ||
} | ||
if (prefixArray2.length === 0) { | ||
prefixManifest[value.prefix] = false; | ||
const messageObject = { | ||
line: value.line, | ||
column: value.column || 1, | ||
message: formattedMessage, | ||
severity, | ||
}; | ||
if (!containsObject(messageObject, prefixedViolations[value.prefix])) { | ||
prefixedViolations[value.prefix].push(messageObject); | ||
} | ||
} | ||
} | ||
// Delete the array of messages for prefix keys that *were* found | ||
for (const prefix in prefixManifest) { | ||
if (Object.hasOwn(prefixManifest, prefix)) { | ||
if (prefixManifest[prefix]) { | ||
delete prefixedViolations[prefix]; | ||
} else { | ||
for (const value of prefixedViolations[prefix]) { | ||
violations.push(value); | ||
} | ||
} | ||
} | ||
} | ||
return violations; | ||
export { | ||
check, checkFile, comparePrefixArrays, formatMessageText, formatter, getFiles, readFile, writeFile, writeOutput, | ||
}; | ||
/** | ||
* Reads a file from the filesystem and normalises its line endings. | ||
* | ||
* @param {string} filePath | ||
* | ||
* @returns {string} | ||
*/ | ||
export const readFile = (filePath) => readFileSync(filePath, 'utf8').replace(/\r\n/, '\n'); | ||
/** | ||
* Checks a file for missing paths. | ||
* | ||
* @param {string} filePath - The path of the file to check. | ||
* | ||
* @returns {Array<object>} | ||
*/ | ||
export const checkFile = (filePath) => { | ||
const templatePathViolations = []; | ||
const taglibPathViolations = []; | ||
const prefixes = []; | ||
const prefixUsages = []; | ||
let isXMLFile = false; | ||
let lineNumber = 1; | ||
const lines = readFile(filePath).split('\n'); | ||
// Cache the dirname of the file being analysed | ||
const fileDirname = path.dirname(filePath); | ||
for (const line of lines) { | ||
isXMLFile = checkIsXMLFile(line); | ||
// Exclude @usage doc lines including code snippets | ||
const isUsageLine = line.startsWith('@usage'); | ||
const importSearch = line.match(/prefix=["'](?<import>[A-Za-z\d]+)["']/); | ||
if (importSearch !== null) { | ||
prefixes.push({ | ||
prefix: importSearch.groups.import, | ||
line: lineNumber, | ||
column: importSearch.index + 1, | ||
}); | ||
} | ||
const namespaceSearch = matchAll(line, /<(?<namespace>[A-Za-z\d]+):/g); | ||
// We're outputting an XML file - don't attempt to collate namespace prefix usages | ||
if (!isXMLFile && !isUsageLine) { | ||
for (const value of namespaceSearch) { | ||
prefixUsages.push({ | ||
prefix: value.groups.namespace, | ||
line: lineNumber, | ||
column: value.index + 1, | ||
}); | ||
} | ||
} | ||
const taglibMatches = matchAll(line, /taglib=["'](?<taglib>[^"']+)["']/g); | ||
for (const taglibMatch of taglibMatches) { | ||
let taglibPath = taglibMatch.groups.taglib; | ||
if (!path.isAbsolute(taglibPath)) { | ||
taglibPath = path.resolve(fileDirname, taglibPath); | ||
} | ||
if (!existsSync(taglibPath)) { | ||
taglibPathViolations.push({ | ||
line: lineNumber, | ||
column: taglibMatch.index + 1, | ||
message: `cfimport taglib path ${taglibPath} not found`, | ||
severity: 'error', | ||
}); | ||
} | ||
} | ||
// Checks <cfinclude template="$path" /> | ||
const cfIncludeMatches = matchAll(line, /template=["'](?<path>[^"']+)["']/g); | ||
// Checks include '$path'; (inside <cfscript>) | ||
// @TODO fix vulnerable RegExp | ||
// eslint-disable-next-line redos/no-vulnerable | ||
const includeMatches = matchAll(line, /\binclude\s['"](?<path>.*\.cfm)['"]/g); | ||
for (const includeMatch of [...cfIncludeMatches, ...includeMatches]) { | ||
// Dynamic path (contains # or &): all we can check is the non-dynamic part, | ||
// wound back to the last slash | ||
let templatePath = includeMatch.groups.path; | ||
const hashPos = templatePath.indexOf('#'); | ||
const ampersandPos = templatePath.indexOf('&'); | ||
if (hashPos !== -1 || ampersandPos !== -1) { | ||
const searchPos = hashPos === -1 ? ampersandPos : hashPos; | ||
const lastSlashPos = templatePath.lastIndexOf('/', searchPos); | ||
templatePath = path.dirname(templatePath.slice(0, lastSlashPos)); | ||
} | ||
// Can't work with webroot-virtual paths, e.g. /missing.cfm | ||
if (templatePath.slice(0, 1) !== '/') { | ||
if (!path.isAbsolute(templatePath)) { | ||
// Resolve the templatePath relative to the dirname of the including file | ||
templatePath = path.resolve(fileDirname, templatePath); | ||
} | ||
if (!existsSync(templatePath)) { | ||
templatePathViolations.push({ | ||
line: lineNumber, | ||
column: includeMatch.index + 1, | ||
message: `cfinclude/include template path ${templatePath} not found`, | ||
severity: 'error', | ||
}); | ||
} | ||
} | ||
} | ||
lineNumber += 1; | ||
} | ||
const unusedPrefixViolations = comparePrefixArrays( | ||
prefixes, | ||
prefixUsages, | ||
'cfimported namespace prefix "{2}" not used', | ||
'warning', | ||
); | ||
const unimportedPrefixViolations = comparePrefixArrays( | ||
prefixUsages, | ||
prefixes, | ||
'used namespace prefix "{2}" not cfimported', | ||
'error', | ||
); | ||
return [ | ||
...unimportedPrefixViolations, | ||
...unusedPrefixViolations, | ||
...templatePathViolations, | ||
...taglibPathViolations, | ||
]; | ||
}; | ||
/** | ||
* Gets a list of files from a given path. | ||
* | ||
* @param {string} filePath | ||
* | ||
* @returns {Array<string>} | ||
*/ | ||
export const getFiles = (filePath) => { | ||
let fileNames = []; | ||
// Resolve the path, if a relative path was passed in | ||
if (!path.isAbsolute(filePath)) { | ||
filePath = path.resolve(filePath); | ||
} | ||
// The path exists... | ||
if (existsSync(filePath)) { | ||
try { | ||
// ...try a readdirSync and... | ||
readdirSync(filePath); | ||
// (Add a trailing slash if not present) | ||
if (filePath.slice(-1) !== '/') { | ||
filePath += '/'; | ||
} | ||
fileNames = sync(`${filePath}**/*.cfm`, { | ||
ignore: ['**/WEB-INF/**', '**/node_modules/**'], | ||
}); | ||
} catch { | ||
// ...if that fails, it's a file, not a directory | ||
fileNames = [filePath]; | ||
} | ||
} | ||
return fileNames; | ||
}; | ||
/** | ||
* Checks a file or files for path errors. | ||
* | ||
* @param {string} filePath - The full path of the file to check. | ||
* | ||
* @returns {Array<object>} | ||
*/ | ||
export const check = (filePath) => { | ||
const violations = []; | ||
const fileNames = getFiles(filePath); | ||
// Loop over our file list, checking each file | ||
for (const fileName of fileNames) { | ||
const fileViolations = checkFile(fileName); | ||
if (fileViolations.length > 0) { | ||
violations.push({ | ||
filename: fileName, | ||
messages: fileViolations, | ||
}); | ||
} | ||
} | ||
return violations; | ||
}; | ||
/** | ||
* @param {Array<object>} violations - The violations array. | ||
* @param {string} format - The output format to use. | ||
* | ||
* @returns {string|Array<object>} | ||
*/ | ||
export const formatter = (violations, format) => format === 'checkstyle' | ||
? checkstyleFormatter(violations) | ||
: violations; | ||
/** | ||
* @param {string} output - The file contents to write. | ||
* @param {string} outFile - The file to write to. | ||
*/ | ||
export const writeFile = (output, outFile) => { | ||
// Resolve the path if it's not absolute | ||
if (!path.isAbsolute(outFile)) { | ||
outFile = path.resolve(outFile); | ||
} | ||
// Warn that the target directory doesn't exist | ||
if (existsSync(path.dirname(outFile))) { | ||
writeFileSync(outFile, output, 'utf8'); | ||
} else { | ||
console.warn(`Cannot write ${outFile}. Destination directory doesn’t exist`); | ||
} | ||
}; | ||
/** | ||
* @param {string|Array<object>} output - The output to write. | ||
*/ | ||
export const writeOutput = (output) => { | ||
if (Array.isArray(output)) { | ||
for (const violation of output) { | ||
console.log(`File: ${chalk.green(violation.filename)}`); | ||
for (const message of violation.messages) { | ||
let messageText = `L${message.line}:${message.column} - ${message.message}`; | ||
if (message.severity === 'error') { | ||
messageText = chalk.red(messageText); | ||
} else if (message.severity === 'warning') { | ||
messageText = chalk.yellow(messageText); | ||
} | ||
console.log(' ', logSymbols[message.severity], messageText); | ||
} | ||
} | ||
} else { | ||
console.log(output); | ||
} | ||
}; |
import deepEqual from 'deep-equal'; | ||
import path from 'path'; | ||
@@ -48,1 +49,15 @@ /** | ||
}; | ||
/** | ||
* | ||
* @param {string} filePath - The path to normalise | ||
* @returns {string} | ||
*/ | ||
export const normalisePath = (filePath) => { | ||
// Resolve the path, if a relative path was passed in | ||
if (!path.isAbsolute(filePath)) { | ||
filePath = path.resolve(filePath); | ||
} | ||
return filePath; | ||
}; |
@@ -5,3 +5,3 @@ { | ||
"description": "Check CFML files for correct paths in cfinclude/cfimport tags", | ||
"version": "10.0.2", | ||
"version": "10.1.0", | ||
"homepage": "https://github.com/timbeadle/cfpathcheck", | ||
@@ -37,6 +37,6 @@ "author": { | ||
"engines": { | ||
"node": ">= 18" | ||
"node": ">= 18.20.3" | ||
}, | ||
"scripts": { | ||
"build:types": "tsc", | ||
"build:types": "rimraf ./dist/* && tsc", | ||
"test": "run-p test:snyk test:lint test:unit test:engines", | ||
@@ -53,3 +53,3 @@ "test:ci": "run-p test:lint test:unit test:engines", | ||
"dependencies": { | ||
"@snyk/protect": "^1.1284.0", | ||
"@snyk/protect": "^1.1292.1", | ||
"chalk": "^5.3.0", | ||
@@ -59,3 +59,3 @@ "checkstyle-formatter": "^1.1.0", | ||
"deep-equal": "^2.2.3", | ||
"glob": "^10.3.10", | ||
"glob": "^10.4.5", | ||
"log-symbols": "^6.0.0", | ||
@@ -65,3 +65,3 @@ "minimist": "^1.2.8" | ||
"devDependencies": { | ||
"@types/chai": "4.3.12", | ||
"@types/chai": "4.3.16", | ||
"@types/checkstyle-formatter": "1.0.2", | ||
@@ -71,21 +71,22 @@ "@types/deep-equal": "1.0.4", | ||
"@types/minimist": "1.2.5", | ||
"@types/mocha": "10.0.6", | ||
"@types/node": "^20.11.28", | ||
"chai": "5.1.0", | ||
"@types/mocha": "10.0.7", | ||
"@types/node": "^22.0.0", | ||
"chai": "5.1.1", | ||
"eslint": "8.57.0", | ||
"eslint-config-xo": "0.44.0", | ||
"eslint-config-xo": "0.45.0", | ||
"eslint-plugin-import": "2.29.1", | ||
"eslint-plugin-redos": "4.4.5", | ||
"ls-engines": "0.9.1", | ||
"mocha": "10.3.0", | ||
"npm-run-all2": "6.1.2", | ||
"nyc": "15.1.0", | ||
"prettier": "3.2.5", | ||
"typescript": "^5.4.2" | ||
"ls-engines": "0.9.2", | ||
"mocha": "10.7.0", | ||
"npm-run-all2": "6.2.2", | ||
"nyc": "17.0.0", | ||
"prettier": "3.3.3", | ||
"rimraf": "^5.0.9", | ||
"typescript": "^5.5.4" | ||
}, | ||
"snyk": true, | ||
"volta": { | ||
"node": "18.18.2", | ||
"npm": "10.5.0" | ||
"node": "18.20.3", | ||
"npm": "10.8.2" | ||
} | ||
} |
@@ -21,4 +21,5 @@ { | ||
"esModuleInterop": true, | ||
"target": "ES2015" | ||
"target": "ES2015", | ||
"moduleResolution": "Node" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
979527
30
459
19
2
1
Updated@snyk/protect@^1.1292.1
Updatedglob@^10.4.5