-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from sasjs/line-ending-formatting
feat(*): add line endings rule, add automatic formatting for fixable violations
- Loading branch information
Showing
42 changed files
with
1,409 additions
and
258 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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;` | ||
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;` | ||
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')) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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;` | ||
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;` | ||
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')) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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;` | ||
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.' | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { formatText } from './formatText' | ||
import * as getLintConfigModule from '../utils/getLintConfig' | ||
import { LintConfig } from '../types' | ||
jest.mock('../utils/getLintConfig') | ||
|
||
describe('formatText', () => { | ||
it('should format the given text based on configured rules', async () => { | ||
jest | ||
.spyOn(getLintConfigModule, 'getLintConfig') | ||
.mockImplementationOnce(() => | ||
Promise.resolve( | ||
new LintConfig(getLintConfigModule.DefaultLintConfiguration) | ||
) | ||
) | ||
const text = `%macro test | ||
%put 'hello';\r\n%mend; ` | ||
|
||
const expectedOutput = `/** | ||
@file | ||
@brief <Your brief here> | ||
<h4> SAS Macros </h4> | ||
**/\n%macro test | ||
%put 'hello';\n%mend test;` | ||
|
||
const output = await formatText(text) | ||
|
||
expect(output).toEqual(expectedOutput) | ||
}) | ||
|
||
it('should use CRLF line endings when configured', async () => { | ||
jest | ||
.spyOn(getLintConfigModule, 'getLintConfig') | ||
.mockImplementationOnce(() => | ||
Promise.resolve( | ||
new LintConfig({ | ||
...getLintConfigModule.DefaultLintConfiguration, | ||
lineEndings: 'crlf' | ||
}) | ||
) | ||
) | ||
const text = `%macro test\n %put 'hello';\r\n%mend; ` | ||
|
||
const expectedOutput = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro test\r\n %put 'hello';\r\n%mend test;` | ||
|
||
const output = await formatText(text) | ||
|
||
expect(output).toEqual(expectedOutput) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { getLintConfig } from '../utils' | ||
import { processText } from './shared' | ||
|
||
export const formatText = async (text: string) => { | ||
const config = await getLintConfig() | ||
return processText(text, config) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export * from './formatText' | ||
export * from './formatFile' | ||
export * from './formatFolder' | ||
export * from './formatProject' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { LintConfig } from '../types' | ||
import { LineEndings } from '../types/LineEndings' | ||
import { splitText } from '../utils/splitText' | ||
|
||
export const processText = (text: string, config: LintConfig) => { | ||
const processedText = processContent(config, text) | ||
const lines = splitText(processedText, config) | ||
const formattedLines = lines.map((line) => { | ||
return processLine(config, line) | ||
}) | ||
|
||
const configuredLineEnding = | ||
config.lineEndings === LineEndings.LF ? '\n' : '\r\n' | ||
return formattedLines.join(configuredLineEnding) | ||
} | ||
|
||
const processContent = (config: LintConfig, content: string): string => { | ||
let processedContent = content | ||
config.fileLintRules | ||
.filter((r) => !!r.fix) | ||
.forEach((rule) => { | ||
processedContent = rule.fix!(processedContent) | ||
}) | ||
|
||
return processedContent | ||
} | ||
|
||
export const processLine = (config: LintConfig, line: string): string => { | ||
let processedLine = line | ||
config.lineLintRules | ||
.filter((r) => !!r.fix) | ||
.forEach((rule) => { | ||
processedLine = rule.fix!(line) | ||
}) | ||
|
||
return processedLine | ||
} |
Oops, something went wrong.