diff --git a/package-lock.json b/package-lock.json index d184b1d..6743da7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -648,9 +648,9 @@ } }, "@sasjs/utils": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.9.0.tgz", - "integrity": "sha512-j7ssEmb8OSZHUUL0PGVgoby0j0ClCcsLsydDCk/C4OAoWPAUPFI5HgGFPSEipz9+P8OlL/EBnglj4LGtlFHCpw==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.10.1.tgz", + "integrity": "sha512-T54jx6NEMLu2+R/ux4qcb3dDJ7nFrKkPCkmPXEfZxPQBkbq4C0kmaZv6dC63RDH68wYhoXR2S5fION5fFh91iw==", "requires": { "@types/prompts": "^2.0.9", "consola": "^2.15.0", @@ -778,9 +778,9 @@ "dev": true }, "@types/prompts": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.9.tgz", - "integrity": "sha512-TORZP+FSjTYMWwKadftmqEn6bziN5RnfygehByGsjxoK5ydnClddtv6GikGWPvCm24oI+YBwck5WDxIIyNxUrA==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.10.tgz", + "integrity": "sha512-W3PEl3l4vmxdgfY6LUG7ysh+mLJOTOFYmSpiLe6MCo1OdEm8b5s6ZJfuTQgEpYNwcMiiaRzJespPS5Py2tqLlQ==", "requires": { "@types/node": "*" } diff --git a/package.json b/package.json index 7b621a5..8a03b3b 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,6 @@ "typescript": "^4.2.3" }, "dependencies": { - "@sasjs/utils": "^2.9.0" + "@sasjs/utils": "^2.10.1" } } diff --git a/src/lint.ts b/src/lint.ts index 8aba8df..c7bb5c9 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -1,7 +1,20 @@ -import { readFile } from '@sasjs/utils/file' +import { readFile, listSubFoldersInFolder } from '@sasjs/utils/file' import { Diagnostic } from './types/Diagnostic' import { LintConfig } from './types/LintConfig' +import { asyncForEach } from './utils/asyncForEach' import { getLintConfig } from './utils/getLintConfig' +import { listSasFiles } from './utils/listSasFiles' +import path from 'path' +import { getProjectRoot } from './utils' + +const excludeFolders = [ + '.git', + '.github', + '.vscode', + 'node_modules', + 'sasjsbuild', + 'sasjsresults' +] /** * Analyses and produces a set of diagnostics for the given text content. @@ -16,10 +29,14 @@ export const lintText = async (text: string) => { /** * Analyses and produces a set of diagnostics for the file at the given path. * @param {string} filePath - the path to the file to be linted. + * @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file. * @returns {Diagnostic[]} array of diagnostic objects, each containing a warning, line number and column number. */ -export const lintFile = async (filePath: string) => { - const config = await getLintConfig() +export const lintFile = async ( + filePath: string, + configuration?: LintConfig +) => { + const config = configuration || (await getLintConfig()) const text = await readFile(filePath) const fileDiagnostics = processFile(filePath, config) @@ -28,6 +45,47 @@ export const lintFile = async (filePath: string) => { return [...fileDiagnostics, ...textDiagnostics] } +/** + * Analyses and produces a set of diagnostics for the folder at the given path. + * @param {string} folderPath - the path to the folder to be linted. + * @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file. + * @returns {Diagnostic[]} array of diagnostic objects, each containing a warning, line number and column number. + */ +export const lintFolder = async ( + folderPath: string, + configuration?: LintConfig +) => { + const config = configuration || (await getLintConfig()) + const diagnostics: Diagnostic[] = [] + const fileNames = await listSasFiles(folderPath) + await asyncForEach(fileNames, async (fileName) => { + diagnostics.push( + ...(await lintFile(path.join(folderPath, fileName), config)) + ) + }) + + const subFolders = (await listSubFoldersInFolder(folderPath)).filter( + (f: string) => !excludeFolders.includes(f) + ) + + await asyncForEach(subFolders, async (subFolder) => { + diagnostics.push( + ...(await lintFolder(path.join(folderPath, subFolder), config)) + ) + }) + + return diagnostics +} + +/** + * Analyses and produces a set of diagnostics for the current project. + * @returns {Diagnostic[]} array of diagnostic objects, each containing a warning, line number and column number. + */ +export const lintProject = async () => { + const projectRoot = await getProjectRoot() + return await lintFolder(projectRoot) +} + /** * Splits the given content into a list of lines, regardless of CRLF or LF line endings. * @param {string} text - the text content to be split into lines. diff --git a/src/utils/asyncForEach.ts b/src/utils/asyncForEach.ts new file mode 100644 index 0000000..744052b --- /dev/null +++ b/src/utils/asyncForEach.ts @@ -0,0 +1,8 @@ +export async function asyncForEach( + array: any[], + callback: (item: any, index: number, originalArray: any[]) => any +) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array) + } +} diff --git a/src/utils/getLintConfig.ts b/src/utils/getLintConfig.ts index 032fbf0..c90b15d 100644 --- a/src/utils/getLintConfig.ts +++ b/src/utils/getLintConfig.ts @@ -27,6 +27,7 @@ export async function getLintConfig(): Promise { const configuration = await readFile( path.join(projectRoot, '.sasjslint') ).catch((_) => { + console.warn('Unable to load .sasjslint file. Using default configuration.') return JSON.stringify(DefaultLintConfiguration) }) return new LintConfig(JSON.parse(configuration)) diff --git a/src/utils/index.ts b/src/utils/index.ts index 4bb8061..f48b820 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ export * from './getLintConfig' export * from './getProjectRoot' +export * from './listSasFiles' diff --git a/src/utils/listSasFiles.ts b/src/utils/listSasFiles.ts new file mode 100644 index 0000000..47899d6 --- /dev/null +++ b/src/utils/listSasFiles.ts @@ -0,0 +1,6 @@ +import { listFilesInFolder } from '@sasjs/utils/file' + +export const listSasFiles = async (folderPath: string): Promise => { + const files = await listFilesInFolder(folderPath) + return files.filter((f) => f.endsWith('.sas')) +}