1
0
mirror of https://github.com/sasjs/lint.git synced 2025-12-10 17:34:36 +00:00

Merge branch 'main' into dependabot/npm_and_yarn/types/node-15.0.2

This commit is contained in:
Krishna Acondy
2021-05-06 07:25:32 +01:00
committed by GitHub
7 changed files with 314 additions and 21 deletions

View File

@@ -8,22 +8,76 @@ describe('formatFile', () => {
const content = `%macro somemacro(); \n%put 'hello';\n%mend;` 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;` const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
await createFile(path.join(__dirname, 'format-file-test.sas'), content) await createFile(path.join(__dirname, 'format-file-test.sas'), content)
const expectedResult = {
updatedFilePaths: [path.join(__dirname, 'format-file-test.sas')],
fixedDiagnosticsCount: 3,
unfixedDiagnostics: []
}
await formatFile(path.join(__dirname, 'format-file-test.sas')) const result = await formatFile(
const result = await readFile(path.join(__dirname, 'format-file-test.sas')) path.join(__dirname, 'format-file-test.sas')
)
const formattedContent = await readFile(
path.join(__dirname, 'format-file-test.sas')
)
expect(result).toEqual(expectedContent) expect(result).toEqual(expectedResult)
expect(formattedContent).toEqual(expectedContent)
await deleteFile(path.join(__dirname, 'format-file-test.sas')) await deleteFile(path.join(__dirname, 'format-file-test.sas'))
}) })
it('should use the provided config if available', async () => { it('should use the provided config if available', async () => {
const content = `%macro somemacro(); \n%put 'hello';\n%mend;` 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;` 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;`
const expectedResult = {
updatedFilePaths: [path.join(__dirname, 'format-file-config.sas')],
fixedDiagnosticsCount: 2,
unfixedDiagnostics: [
{
endColumnNumber: 7,
lineNumber: 8,
message: '%mend statement is missing macro name - somemacro',
severity: 1,
startColumnNumber: 1
}
]
}
await createFile(path.join(__dirname, 'format-file-config.sas'), content) await createFile(path.join(__dirname, 'format-file-config.sas'), content)
await formatFile( const result = await formatFile(
path.join(__dirname, 'format-file-config.sas'), path.join(__dirname, 'format-file-config.sas'),
new LintConfig({
lineEndings: 'crlf',
hasMacroNameInMend: false,
hasDoxygenHeader: true,
noTrailingSpaces: true
})
)
const formattedContent = await readFile(
path.join(__dirname, 'format-file-config.sas')
)
expect(result).toEqual(expectedResult)
expect(formattedContent).toEqual(expectedContent)
await deleteFile(path.join(__dirname, 'format-file-config.sas'))
})
it('should not update any files if there are no formatting violations', async () => {
const content = `/**\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;`
const expectedResult = {
updatedFilePaths: [],
fixedDiagnosticsCount: 0,
unfixedDiagnostics: []
}
await createFile(
path.join(__dirname, 'format-file-no-violations.sas'),
content
)
const result = await formatFile(
path.join(__dirname, 'format-file-no-violations.sas'),
new LintConfig({ new LintConfig({
lineEndings: 'crlf', lineEndings: 'crlf',
hasMacroNameInMend: true, hasMacroNameInMend: true,
@@ -31,12 +85,13 @@ describe('formatFile', () => {
noTrailingSpaces: true noTrailingSpaces: true
}) })
) )
const result = await readFile( const formattedContent = await readFile(
path.join(__dirname, 'format-file-config.sas') path.join(__dirname, 'format-file-no-violations.sas')
) )
expect(result).toEqual(expectedContent) expect(result).toEqual(expectedResult)
expect(formattedContent).toEqual(content)
await deleteFile(path.join(__dirname, 'format-file-config.sas')) await deleteFile(path.join(__dirname, 'format-file-no-violations.sas'))
}) })
}) })

View File

@@ -1,4 +1,6 @@
import { createFile, readFile } from '@sasjs/utils/file' import { createFile, readFile } from '@sasjs/utils/file'
import { lintFile } from '../lint'
import { FormatResult } from '../types'
import { LintConfig } from '../types/LintConfig' import { LintConfig } from '../types/LintConfig'
import { getLintConfig } from '../utils/getLintConfig' import { getLintConfig } from '../utils/getLintConfig'
import { processText } from './shared' import { processText } from './shared'
@@ -7,16 +9,37 @@ import { processText } from './shared'
* Applies automatic formatting to the file at the given path. * Applies automatic formatting to the file at the given path.
* @param {string} filePath - the path to the file to be formatted. * @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. * @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. * @returns {Promise<FormatResult>} Resolves successfully when the file has been formatted.
*/ */
export const formatFile = async ( export const formatFile = async (
filePath: string, filePath: string,
configuration?: LintConfig configuration?: LintConfig
) => { ): Promise<FormatResult> => {
const config = configuration || (await getLintConfig()) const config = configuration || (await getLintConfig())
const diagnosticsBeforeFormat = await lintFile(filePath)
const diagnosticsCountBeforeFormat = diagnosticsBeforeFormat.length
const text = await readFile(filePath) const text = await readFile(filePath)
const formattedText = processText(text, config) const formattedText = processText(text, config)
await createFile(filePath, formattedText) await createFile(filePath, formattedText)
const diagnosticsAfterFormat = await lintFile(filePath)
const diagnosticsCountAfterFormat = diagnosticsAfterFormat.length
const fixedDiagnosticsCount =
diagnosticsCountBeforeFormat - diagnosticsCountAfterFormat
const updatedFilePaths: string[] = []
if (fixedDiagnosticsCount) {
updatedFilePaths.push(filePath)
}
return {
updatedFilePaths,
fixedDiagnosticsCount,
unfixedDiagnostics: diagnosticsAfterFormat
}
} }

View File

@@ -6,23 +6,39 @@ import {
deleteFolder, deleteFolder,
readFile readFile
} from '@sasjs/utils/file' } from '@sasjs/utils/file'
import { Diagnostic, LintConfig } from '../types'
describe('formatFolder', () => { describe('formatFolder', () => {
it('should fix linting issues in a given folder', async () => { it('should fix linting issues in a given folder', async () => {
const content = `%macro somemacro(); \n%put 'hello';\n%mend;` 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;` const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
const expectedResult = {
updatedFilePaths: [
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
],
fixedDiagnosticsCount: 3,
unfixedDiagnostics: new Map<string, Diagnostic[]>([
[
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
[]
]
])
}
await createFolder(path.join(__dirname, 'format-folder-test')) await createFolder(path.join(__dirname, 'format-folder-test'))
await createFile( await createFile(
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'), path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
content content
) )
await formatFolder(path.join(__dirname, 'format-folder-test')) const result = await formatFolder(
const result = await readFile( path.join(__dirname, 'format-folder-test')
)
const formattedContent = await readFile(
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas') path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
) )
expect(result).toEqual(expectedContent) expect(formattedContent).toEqual(expectedContent)
expect(result).toEqual(expectedResult)
await deleteFolder(path.join(__dirname, 'format-folder-test')) await deleteFolder(path.join(__dirname, 'format-folder-test'))
}) })
@@ -30,6 +46,29 @@ describe('formatFolder', () => {
it('should fix linting issues in subfolders of a given folder', async () => { it('should fix linting issues in subfolders of a given folder', async () => {
const content = `%macro somemacro(); \n%put 'hello';\n%mend;` 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;` const expectedContent = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
const expectedResult = {
updatedFilePaths: [
path.join(
__dirname,
'format-folder-test',
'subfolder',
'format-folder-test.sas'
)
],
fixedDiagnosticsCount: 3,
unfixedDiagnostics: new Map<string, Diagnostic[]>([
[
path.join(
__dirname,
'format-folder-test',
'subfolder',
'format-folder-test.sas'
),
[]
]
])
}
await createFolder(path.join(__dirname, 'format-folder-test')) await createFolder(path.join(__dirname, 'format-folder-test'))
await createFolder(path.join(__dirname, 'subfolder')) await createFolder(path.join(__dirname, 'subfolder'))
await createFile( await createFile(
@@ -42,8 +81,10 @@ describe('formatFolder', () => {
content content
) )
await formatFolder(path.join(__dirname, 'format-folder-test')) const result = await formatFolder(
const result = await readFile( path.join(__dirname, 'format-folder-test')
)
const formattedContent = await readFile(
path.join( path.join(
__dirname, __dirname,
'format-folder-test', 'format-folder-test',
@@ -52,7 +93,135 @@ describe('formatFolder', () => {
) )
) )
expect(result).toEqual(expectedContent) expect(result).toEqual(expectedResult)
expect(formattedContent).toEqual(expectedContent)
await deleteFolder(path.join(__dirname, 'format-folder-test'))
})
it('should use a custom configuration when provided', 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;`
const expectedResult = {
updatedFilePaths: [
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
],
fixedDiagnosticsCount: 3,
unfixedDiagnostics: new Map<string, Diagnostic[]>([
[
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
[]
]
])
}
await createFolder(path.join(__dirname, 'format-folder-test'))
await createFile(
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
content
)
const result = await formatFolder(
path.join(__dirname, 'format-folder-test'),
new LintConfig({
lineEndings: 'crlf',
hasMacroNameInMend: false,
hasDoxygenHeader: true,
noTrailingSpaces: true
})
)
const formattedContent = await readFile(
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
)
expect(formattedContent).toEqual(expectedContent)
expect(result).toEqual(expectedResult)
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;`
const expectedResult = {
updatedFilePaths: [
path.join(
__dirname,
'format-folder-test',
'subfolder',
'format-folder-test.sas'
)
],
fixedDiagnosticsCount: 3,
unfixedDiagnostics: new Map<string, Diagnostic[]>([
[
path.join(
__dirname,
'format-folder-test',
'subfolder',
'format-folder-test.sas'
),
[]
]
])
}
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
)
const result = await formatFolder(
path.join(__dirname, 'format-folder-test')
)
const formattedContent = await readFile(
path.join(
__dirname,
'format-folder-test',
'subfolder',
'format-folder-test.sas'
)
)
expect(result).toEqual(expectedResult)
expect(formattedContent).toEqual(expectedContent)
await deleteFolder(path.join(__dirname, 'format-folder-test'))
})
it('should not update any files when there are no violations', async () => {
const content = `/**\n @file\n @brief <Your brief here>\n <h4> SAS Macros </h4>\n**/\n%macro somemacro();\n%put 'hello';\n%mend somemacro;`
const expectedResult = {
updatedFilePaths: [],
fixedDiagnosticsCount: 0,
unfixedDiagnostics: new Map<string, Diagnostic[]>([
[
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
[]
]
])
}
await createFolder(path.join(__dirname, 'format-folder-test'))
await createFile(
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas'),
content
)
const result = await formatFolder(
path.join(__dirname, 'format-folder-test')
)
const formattedContent = await readFile(
path.join(__dirname, 'format-folder-test', 'format-folder-test.sas')
)
expect(formattedContent).toEqual(content)
expect(result).toEqual(expectedResult)
await deleteFolder(path.join(__dirname, 'format-folder-test')) await deleteFolder(path.join(__dirname, 'format-folder-test'))
}) })

View File

@@ -1,5 +1,7 @@
import { listSubFoldersInFolder } from '@sasjs/utils/file' import { listSubFoldersInFolder } from '@sasjs/utils/file'
import path from 'path' import path from 'path'
import { lintFolder } from '../lint'
import { FormatResult } from '../types'
import { LintConfig } from '../types/LintConfig' import { LintConfig } from '../types/LintConfig'
import { asyncForEach } from '../utils/asyncForEach' import { asyncForEach } from '../utils/asyncForEach'
import { getLintConfig } from '../utils/getLintConfig' import { getLintConfig } from '../utils/getLintConfig'
@@ -19,13 +21,18 @@ const excludeFolders = [
* Automatically formats all SAS files in the folder at the given path. * Automatically formats all SAS files in the folder at the given path.
* @param {string} folderPath - the path to the folder to be formatted. * @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. * @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. * @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the given folder have been formatted.
*/ */
export const formatFolder = async ( export const formatFolder = async (
folderPath: string, folderPath: string,
configuration?: LintConfig configuration?: LintConfig
) => { ): Promise<FormatResult> => {
const config = configuration || (await getLintConfig()) const config = configuration || (await getLintConfig())
const diagnosticsBeforeFormat = await lintFolder(folderPath)
const diagnosticsCountBeforeFormat = Array.from(
diagnosticsBeforeFormat.values()
).reduce((a, b) => a + b.length, 0)
const fileNames = await listSasFiles(folderPath) const fileNames = await listSasFiles(folderPath)
await asyncForEach(fileNames, async (fileName) => { await asyncForEach(fileNames, async (fileName) => {
const filePath = path.join(folderPath, fileName) const filePath = path.join(folderPath, fileName)
@@ -39,4 +46,29 @@ export const formatFolder = async (
await asyncForEach(subFolders, async (subFolder) => { await asyncForEach(subFolders, async (subFolder) => {
await formatFolder(path.join(folderPath, subFolder), config) await formatFolder(path.join(folderPath, subFolder), config)
}) })
const diagnosticsAfterFormat = await lintFolder(folderPath)
const diagnosticsCountAfterFormat = Array.from(
diagnosticsAfterFormat.values()
).reduce((a, b) => a + b.length, 0)
const fixedDiagnosticsCount =
diagnosticsCountBeforeFormat - diagnosticsCountAfterFormat
const updatedFilePaths: string[] = []
Array.from(diagnosticsBeforeFormat.keys()).forEach((filePath) => {
const diagnosticsBefore = diagnosticsBeforeFormat.get(filePath) || []
const diagnosticsAfter = diagnosticsAfterFormat.get(filePath) || []
if (diagnosticsBefore.length !== diagnosticsAfter.length) {
updatedFilePaths.push(filePath)
}
})
return {
updatedFilePaths,
fixedDiagnosticsCount,
unfixedDiagnostics: diagnosticsAfterFormat
}
} }

View File

@@ -1,15 +1,18 @@
import { lintFolder } from '../lint/lintFolder'
import { FormatResult } from '../types/FormatResult'
import { getProjectRoot } from '../utils/getProjectRoot' import { getProjectRoot } from '../utils/getProjectRoot'
import { formatFolder } from './formatFolder' import { formatFolder } from './formatFolder'
/** /**
* Automatically formats all SAS files in the current project. * 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. * @returns {Promise<FormatResult>} Resolves successfully when all SAS files in the current project have been formatted.
*/ */
export const formatProject = async () => { export const formatProject = async (): Promise<FormatResult> => {
const projectRoot = const projectRoot =
(await getProjectRoot()) || process.projectDir || process.currentDir (await getProjectRoot()) || process.projectDir || process.currentDir
if (!projectRoot) { if (!projectRoot) {
throw new Error('SASjs Project Root was not found.') throw new Error('SASjs Project Root was not found.')
} }
return await formatFolder(projectRoot) return await formatFolder(projectRoot)
} }

10
src/types/FormatResult.ts Normal file
View File

@@ -0,0 +1,10 @@
import { Diagnostic } from './Diagnostic'
/**
* Represents the result of a format operation on a file, folder or project.
*/
export interface FormatResult {
updatedFilePaths: string[]
fixedDiagnosticsCount: number
unfixedDiagnostics: Map<string, Diagnostic[]> | Diagnostic[]
}

View File

@@ -1,4 +1,5 @@
export * from './Diagnostic' export * from './Diagnostic'
export * from './FormatResult'
export * from './LintConfig' export * from './LintConfig'
export * from './LintRule' export * from './LintRule'
export * from './LintRuleType' export * from './LintRuleType'