mirror of
https://github.com/sasjs/lint.git
synced 2026-01-16 08:40:05 +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 { formatText } from './format/formatText'
|
||||||
import { lintText } from './lint'
|
import { lintText } from './lint'
|
||||||
|
|
||||||
@@ -7,15 +9,17 @@ const content = `%put 'Hello';
|
|||||||
%put 'test';
|
%put 'test';
|
||||||
%mend;\r\n`
|
%mend;\r\n`
|
||||||
|
|
||||||
console.log(content)
|
// console.log(content)
|
||||||
lintText(content).then((diagnostics) => {
|
// lintText(content).then((diagnostics) => {
|
||||||
console.log('Before Formatting:')
|
// console.log('Before Formatting:')
|
||||||
console.table(diagnostics)
|
// console.table(diagnostics)
|
||||||
formatText(content).then((formattedText) => {
|
// formatText(content).then((formattedText) => {
|
||||||
lintText(formattedText).then((newDiagnostics) => {
|
// lintText(formattedText).then((newDiagnostics) => {
|
||||||
console.log('After Formatting:')
|
// console.log('After Formatting:')
|
||||||
console.log(formattedText)
|
// console.log(formattedText)
|
||||||
console.table(newDiagnostics)
|
// console.table(newDiagnostics)
|
||||||
})
|
// })
|
||||||
})
|
// })
|
||||||
})
|
// })
|
||||||
|
|
||||||
|
formatFile(path.join(__dirname, 'Example File.sas'))
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from './format'
|
||||||
export * from './lint'
|
export * from './lint'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export * from './utils'
|
export * from './utils'
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { lintFolder } from './lintFolder'
|
import { lintFolder } from './lintFolder'
|
||||||
import { Severity } from '../types/Severity'
|
import { Severity } from '../types/Severity'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import {
|
||||||
|
createFile,
|
||||||
|
createFolder,
|
||||||
|
deleteFolder,
|
||||||
|
readFile
|
||||||
|
} from '@sasjs/utils/file'
|
||||||
|
|
||||||
const expectedFilesCount = 1
|
const expectedFilesCount = 1
|
||||||
const expectedDiagnostics = [
|
const expectedDiagnostics = [
|
||||||
@@ -71,11 +77,18 @@ const expectedDiagnostics = [
|
|||||||
|
|
||||||
describe('lintFolder', () => {
|
describe('lintFolder', () => {
|
||||||
it('should identify lint issues in a given folder', async () => {
|
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)
|
expect(results.size).toEqual(expectedFilesCount)
|
||||||
const diagnostics = results.get(
|
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.length).toEqual(expectedDiagnostics.length)
|
||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
||||||
@@ -87,5 +100,36 @@ describe('lintFolder', () => {
|
|||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[8])
|
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 { Severity } from '../types/Severity'
|
||||||
import * as getProjectRootModule from '../utils/getProjectRoot'
|
import * as getProjectRootModule from '../utils/getProjectRoot'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { createFolder, createFile, readFile, deleteFolder } from '@sasjs/utils'
|
||||||
|
import { DefaultLintConfiguration } from '../utils'
|
||||||
jest.mock('../utils/getProjectRoot')
|
jest.mock('../utils/getProjectRoot')
|
||||||
|
|
||||||
const expectedFilesCount = 1
|
const expectedFilesCount = 1
|
||||||
@@ -73,14 +75,29 @@ const expectedDiagnostics = [
|
|||||||
|
|
||||||
describe('lintProject', () => {
|
describe('lintProject', () => {
|
||||||
it('should identify lint issues in a given project', async () => {
|
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
|
jest
|
||||||
.spyOn(getProjectRootModule, 'getProjectRoot')
|
.spyOn(getProjectRootModule, 'getProjectRoot')
|
||||||
.mockImplementation(() => Promise.resolve(path.join(__dirname, '..')))
|
.mockImplementation(() =>
|
||||||
|
Promise.resolve(path.join(__dirname, 'lint-project-test'))
|
||||||
|
)
|
||||||
const results = await lintProject()
|
const results = await lintProject()
|
||||||
|
|
||||||
expect(results.size).toEqual(expectedFilesCount)
|
expect(results.size).toEqual(expectedFilesCount)
|
||||||
const diagnostics = results.get(
|
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.length).toEqual(expectedDiagnostics.length)
|
||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
expect(diagnostics).toContainEqual(expectedDiagnostics[0])
|
||||||
@@ -92,6 +109,8 @@ describe('lintProject', () => {
|
|||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
expect(diagnostics).toContainEqual(expectedDiagnostics[6])
|
||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
expect(diagnostics).toContainEqual(expectedDiagnostics[7])
|
||||||
expect(diagnostics).toContainEqual(expectedDiagnostics[8])
|
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 () => {
|
it('should throw an error when a project root is not found', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user