1
0
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:
Krishna Acondy
2021-04-19 22:13:53 +01:00
parent d28d32d441
commit bcb50b9968
11 changed files with 320 additions and 17 deletions

View 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
View 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)
}

View 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'))
})
})

View 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)
})
}

View 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.'
)
})
})

View 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
View File

@@ -0,0 +1,4 @@
export * from './formatText'
export * from './formatFile'
export * from './formatFolder'
export * from './formatProject'

View File

@@ -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'))

View File

@@ -1,3 +1,4 @@
export * from './format'
export * from './lint'
export * from './types'
export * from './utils'

View File

@@ -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'))
})
})

View File

@@ -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 () => {