mirror of
https://github.com/sasjs/lint.git
synced 2025-12-10 17:34:36 +00:00
feat(format): add the ability to format files, folders and projects
This commit is contained in:
42
src/format/formatFile.spec.ts
Normal file
42
src/format/formatFile.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { formatFile } from './formatFile'
|
||||
import path from 'path'
|
||||
import { createFile, deleteFile, readFile } from '@sasjs/utils/file'
|
||||
import { LintConfig } from '../types'
|
||||
|
||||
describe('formatFile', () => {
|
||||
it('should fix linting issues in a given file', async () => {
|
||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;\n`
|
||||
await createFile(path.join(__dirname, 'format-file-test.sas'), content)
|
||||
|
||||
await formatFile(path.join(__dirname, 'format-file-test.sas'))
|
||||
const result = await readFile(path.join(__dirname, 'format-file-test.sas'))
|
||||
|
||||
expect(result).toEqual(expectedContent)
|
||||
|
||||
await deleteFile(path.join(__dirname, 'format-file-test.sas'))
|
||||
})
|
||||
|
||||
it('should use the provided config if available', async () => {
|
||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||
const expectedContent = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro somemacro();\r\n%put 'hello';\r\n%mend somemacro;\r\n`
|
||||
await createFile(path.join(__dirname, 'format-file-config.sas'), content)
|
||||
|
||||
await formatFile(
|
||||
path.join(__dirname, 'format-file-config.sas'),
|
||||
new LintConfig({
|
||||
lineEndings: 'crlf',
|
||||
hasMacroNameInMend: true,
|
||||
hasDoxygenHeader: true,
|
||||
noTrailingSpaces: true
|
||||
})
|
||||
)
|
||||
const result = await readFile(
|
||||
path.join(__dirname, 'format-file-config.sas')
|
||||
)
|
||||
|
||||
expect(result).toEqual(expectedContent)
|
||||
|
||||
await deleteFile(path.join(__dirname, 'format-file-config.sas'))
|
||||
})
|
||||
})
|
||||
22
src/format/formatFile.ts
Normal file
22
src/format/formatFile.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createFile, readFile } from '@sasjs/utils/file'
|
||||
import { LintConfig } from '../types/LintConfig'
|
||||
import { getLintConfig } from '../utils/getLintConfig'
|
||||
import { processText } from './shared'
|
||||
|
||||
/**
|
||||
* Applies automatic formatting to the file at the given path.
|
||||
* @param {string} filePath - the path to the file to be formatted.
|
||||
* @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
|
||||
* @returns {Promise<void>} Resolves successfully when the file has been formatted.
|
||||
*/
|
||||
export const formatFile = async (
|
||||
filePath: string,
|
||||
configuration?: LintConfig
|
||||
) => {
|
||||
const config = configuration || (await getLintConfig())
|
||||
const text = await readFile(filePath)
|
||||
|
||||
const formattedText = processText(text, config)
|
||||
|
||||
await createFile(filePath, formattedText)
|
||||
}
|
||||
59
src/format/formatFolder.spec.ts
Normal file
59
src/format/formatFolder.spec.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { formatFolder } from './formatFolder'
|
||||
import path from 'path'
|
||||
import {
|
||||
createFile,
|
||||
createFolder,
|
||||
deleteFolder,
|
||||
readFile
|
||||
} from '@sasjs/utils/file'
|
||||
|
||||
describe('formatFolder', () => {
|
||||
it('should fix linting issues in a given folder', async () => {
|
||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;\n`
|
||||
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||
await createFile(
|
||||
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
|
||||
content
|
||||
)
|
||||
|
||||
await formatFolder(path.join(__dirname, 'format-folder-test'))
|
||||
const result = await readFile(
|
||||
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
|
||||
)
|
||||
|
||||
expect(result).toEqual(expectedContent)
|
||||
|
||||
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||
})
|
||||
|
||||
it('should fix linting issues in subfolders of a given folder', async () => {
|
||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;\n`
|
||||
await createFolder(path.join(__dirname, 'format-folder-test'))
|
||||
await createFolder(path.join(__dirname, 'subfolder'))
|
||||
await createFile(
|
||||
path.join(
|
||||
__dirname,
|
||||
'format-folder-test',
|
||||
'subfolder',
|
||||
'format-folder-test.sas'
|
||||
),
|
||||
content
|
||||
)
|
||||
|
||||
await formatFolder(path.join(__dirname, 'format-folder-test'))
|
||||
const result = await readFile(
|
||||
path.join(
|
||||
__dirname,
|
||||
'format-folder-test',
|
||||
'subfolder',
|
||||
'format-folder-test.sas'
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toEqual(expectedContent)
|
||||
|
||||
await deleteFolder(path.join(__dirname, 'format-folder-test'))
|
||||
})
|
||||
})
|
||||
42
src/format/formatFolder.ts
Normal file
42
src/format/formatFolder.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { listSubFoldersInFolder } from '@sasjs/utils/file'
|
||||
import path from 'path'
|
||||
import { LintConfig } from '../types/LintConfig'
|
||||
import { asyncForEach } from '../utils/asyncForEach'
|
||||
import { getLintConfig } from '../utils/getLintConfig'
|
||||
import { listSasFiles } from '../utils/listSasFiles'
|
||||
import { formatFile } from './formatFile'
|
||||
|
||||
const excludeFolders = [
|
||||
'.git',
|
||||
'.github',
|
||||
'.vscode',
|
||||
'node_modules',
|
||||
'sasjsbuild',
|
||||
'sasjsresults'
|
||||
]
|
||||
|
||||
/**
|
||||
* Automatically formats all SAS files in the folder at the given path.
|
||||
* @param {string} folderPath - the path to the folder to be formatted.
|
||||
* @param {LintConfig} configuration - an optional configuration. When not passed in, this is read from the .sasjslint file.
|
||||
* @returns {Promise<void>} Resolves successfully when all SAS files in the given folder have been formatted.
|
||||
*/
|
||||
export const formatFolder = async (
|
||||
folderPath: string,
|
||||
configuration?: LintConfig
|
||||
) => {
|
||||
const config = configuration || (await getLintConfig())
|
||||
const fileNames = await listSasFiles(folderPath)
|
||||
await asyncForEach(fileNames, async (fileName) => {
|
||||
const filePath = path.join(folderPath, fileName)
|
||||
await formatFile(filePath)
|
||||
})
|
||||
|
||||
const subFolders = (await listSubFoldersInFolder(folderPath)).filter(
|
||||
(f: string) => !excludeFolders.includes(f)
|
||||
)
|
||||
|
||||
await asyncForEach(subFolders, async (subFolder) => {
|
||||
await formatFolder(path.join(folderPath, subFolder), config)
|
||||
})
|
||||
}
|
||||
51
src/format/formatProject.spec.ts
Normal file
51
src/format/formatProject.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { formatProject } from './formatProject'
|
||||
import path from 'path'
|
||||
import {
|
||||
createFile,
|
||||
createFolder,
|
||||
deleteFolder,
|
||||
readFile
|
||||
} from '@sasjs/utils/file'
|
||||
import { DefaultLintConfiguration } from '../utils'
|
||||
import * as getProjectRootModule from '../utils/getProjectRoot'
|
||||
jest.mock('../utils/getProjectRoot')
|
||||
|
||||
describe('formatProject', () => {
|
||||
it('should format files in the current project', async () => {
|
||||
const content = `%macro somemacro(); \n%put 'hello';\n%mend;`
|
||||
const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;\n`
|
||||
await createFolder(path.join(__dirname, 'format-project-test'))
|
||||
await createFile(
|
||||
path.join(__dirname, 'format-project-test', 'format-project-test.sas'),
|
||||
content
|
||||
)
|
||||
await createFile(
|
||||
path.join(__dirname, 'format-project-test', '.sasjslint'),
|
||||
JSON.stringify(DefaultLintConfiguration)
|
||||
)
|
||||
jest
|
||||
.spyOn(getProjectRootModule, 'getProjectRoot')
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve(path.join(__dirname, 'format-project-test'))
|
||||
)
|
||||
|
||||
await formatProject()
|
||||
const result = await readFile(
|
||||
path.join(__dirname, 'format-project-test', 'format-project-test.sas')
|
||||
)
|
||||
|
||||
expect(result).toEqual(expectedContent)
|
||||
|
||||
await deleteFolder(path.join(__dirname, 'format-project-test'))
|
||||
})
|
||||
|
||||
it('should throw an error when a project root is not found', async () => {
|
||||
jest
|
||||
.spyOn(getProjectRootModule, 'getProjectRoot')
|
||||
.mockImplementationOnce(() => Promise.resolve(''))
|
||||
|
||||
await expect(formatProject()).rejects.toThrowError(
|
||||
'SASjs Project Root was not found.'
|
||||
)
|
||||
})
|
||||
})
|
||||
15
src/format/formatProject.ts
Normal file
15
src/format/formatProject.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { getProjectRoot } from '../utils/getProjectRoot'
|
||||
import { formatFolder } from './formatFolder'
|
||||
|
||||
/**
|
||||
* Automatically formats all SAS files in the current project.
|
||||
* @returns {Promise<void>} Resolves successfully when all SAS files in the current project have been formatted.
|
||||
*/
|
||||
export const formatProject = async () => {
|
||||
const projectRoot =
|
||||
(await getProjectRoot()) || process.projectDir || process.currentDir
|
||||
if (!projectRoot) {
|
||||
throw new Error('SASjs Project Root was not found.')
|
||||
}
|
||||
return await formatFolder(projectRoot)
|
||||
}
|
||||
4
src/format/index.ts
Normal file
4
src/format/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './formatText'
|
||||
export * from './formatFile'
|
||||
export * from './formatFolder'
|
||||
export * from './formatProject'
|
||||
@@ -1,3 +1,5 @@
|
||||
import { formatFile } from './format/formatFile'
|
||||
import path from 'path'
|
||||
import { formatText } from './format/formatText'
|
||||
import { lintText } from './lint'
|
||||
|
||||
@@ -7,15 +9,17 @@ const content = `%put 'Hello';
|
||||
%put 'test';
|
||||
%mend;\r\n`
|
||||
|
||||
console.log(content)
|
||||
lintText(content).then((diagnostics) => {
|
||||
console.log('Before Formatting:')
|
||||
console.table(diagnostics)
|
||||
formatText(content).then((formattedText) => {
|
||||
lintText(formattedText).then((newDiagnostics) => {
|
||||
console.log('After Formatting:')
|
||||
console.log(formattedText)
|
||||
console.table(newDiagnostics)
|
||||
})
|
||||
})
|
||||
})
|
||||
// console.log(content)
|
||||
// lintText(content).then((diagnostics) => {
|
||||
// console.log('Before Formatting:')
|
||||
// console.table(diagnostics)
|
||||
// formatText(content).then((formattedText) => {
|
||||
// lintText(formattedText).then((newDiagnostics) => {
|
||||
// console.log('After Formatting:')
|
||||
// console.log(formattedText)
|
||||
// console.table(newDiagnostics)
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
|
||||
formatFile(path.join(__dirname, 'Example File.sas'))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './format'
|
||||
export * from './lint'
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { lintFolder } from './lintFolder'
|
||||
import { Severity } from '../types/Severity'
|
||||
import path from 'path'
|
||||
import {
|
||||
createFile,
|
||||
createFolder,
|
||||
deleteFolder,
|
||||
readFile
|
||||
} from '@sasjs/utils/file'
|
||||
|
||||
const expectedFilesCount = 1
|
||||
const expectedDiagnostics = [
|
||||
@@ -71,11 +77,18 @@ const expectedDiagnostics = [
|
||||
|
||||
describe('lintFolder', () => {
|
||||
it('should identify lint issues in a given folder', async () => {
|
||||
const results = await lintFolder(path.join(__dirname, '..'))
|
||||
|
||||
await createFolder(path.join(__dirname, 'lint-folder-test'))
|
||||
const content = await readFile(
|
||||
path.join(__dirname, '..', 'Example File.sas')
|
||||
)
|
||||
await createFile(
|
||||
path.join(__dirname, 'lint-folder-test', 'Example File.sas'),
|
||||
content
|
||||
)
|
||||
const results = await lintFolder(path.join(__dirname, 'lint-folder-test'))
|
||||
expect(results.size).toEqual(expectedFilesCount)
|
||||
const diagnostics = results.get(
|
||||
path.join(__dirname, '..', 'Example File.sas')
|
||||
path.join(__dirname, 'lint-folder-test', 'Example File.sas')
|
||||
)!
|
||||
expect(diagnostics.length).toEqual(expectedDiagnostics.length)
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
||||
@@ -87,5 +100,36 @@ describe('lintFolder', () => {
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[8])
|
||||
|
||||
await deleteFolder(path.join(__dirname, 'lint-folder-test'))
|
||||
})
|
||||
|
||||
it('should identify lint issues in subfolders of a given folder', async () => {
|
||||
await createFolder(path.join(__dirname, 'lint-folder-test'))
|
||||
await createFolder(path.join(__dirname, 'lint-folder-test', 'subfolder'))
|
||||
const content = await readFile(
|
||||
path.join(__dirname, '..', 'Example File.sas')
|
||||
)
|
||||
await createFile(
|
||||
path.join(__dirname, 'lint-folder-test', 'subfolder', 'Example File.sas'),
|
||||
content
|
||||
)
|
||||
const results = await lintFolder(path.join(__dirname, 'lint-folder-test'))
|
||||
expect(results.size).toEqual(expectedFilesCount)
|
||||
const diagnostics = results.get(
|
||||
path.join(__dirname, 'lint-folder-test', 'subfolder', 'Example File.sas')
|
||||
)!
|
||||
expect(diagnostics.length).toEqual(expectedDiagnostics.length)
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[1])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[2])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[3])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[4])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[5])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[8])
|
||||
|
||||
await deleteFolder(path.join(__dirname, 'lint-folder-test'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@ import { lintProject } from './lintProject'
|
||||
import { Severity } from '../types/Severity'
|
||||
import * as getProjectRootModule from '../utils/getProjectRoot'
|
||||
import path from 'path'
|
||||
import { createFolder, createFile, readFile, deleteFolder } from '@sasjs/utils'
|
||||
import { DefaultLintConfiguration } from '../utils'
|
||||
jest.mock('../utils/getProjectRoot')
|
||||
|
||||
const expectedFilesCount = 1
|
||||
@@ -73,14 +75,29 @@ const expectedDiagnostics = [
|
||||
|
||||
describe('lintProject', () => {
|
||||
it('should identify lint issues in a given project', async () => {
|
||||
await createFolder(path.join(__dirname, 'lint-project-test'))
|
||||
const content = await readFile(
|
||||
path.join(__dirname, '..', 'Example File.sas')
|
||||
)
|
||||
await createFile(
|
||||
path.join(__dirname, 'lint-project-test', 'Example File.sas'),
|
||||
content
|
||||
)
|
||||
await createFile(
|
||||
path.join(__dirname, 'lint-project-test', '.sasjslint'),
|
||||
JSON.stringify(DefaultLintConfiguration)
|
||||
)
|
||||
|
||||
jest
|
||||
.spyOn(getProjectRootModule, 'getProjectRoot')
|
||||
.mockImplementation(() => Promise.resolve(path.join(__dirname, '..')))
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve(path.join(__dirname, 'lint-project-test'))
|
||||
)
|
||||
const results = await lintProject()
|
||||
|
||||
expect(results.size).toEqual(expectedFilesCount)
|
||||
const diagnostics = results.get(
|
||||
path.join(__dirname, '..', 'Example File.sas')
|
||||
path.join(__dirname, 'lint-project-test', 'Example File.sas')
|
||||
)!
|
||||
expect(diagnostics.length).toEqual(expectedDiagnostics.length)
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
||||
@@ -92,6 +109,8 @@ describe('lintProject', () => {
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
||||
expect(diagnostics).toContainEqual(expectedDiagnostics[8])
|
||||
|
||||
await deleteFolder(path.join(__dirname, 'lint-project-test'))
|
||||
})
|
||||
|
||||
it('should throw an error when a project root is not found', async () => {
|
||||
|
||||
Reference in New Issue
Block a user