Major refactoring using ESLint with rules from AirBnb and Vue. Enable most of the ESLint rules and do necessary linting in the code. Also add more information for rules that are disabled to describe what they are and why they are disabled. Allow logging (`console.log`) in test files, and in development mode (e.g. when working with `npm run serve`), but disable it when environment is production (as pre-configured by Vue). Also add flag (`--mode production`) in `lint:eslint` command so production linting is executed earlier in lifecycle. Disable rules that requires a separate work. Such as ESLint rules that are broken in TypeScript: no-useless-constructor (eslint/eslint#14118) and no-shadow (eslint/eslint#13014).
167 lines
5.5 KiB
TypeScript
167 lines
5.5 KiB
TypeScript
import { IPipe } from '../IPipe';
|
|
|
|
export class InlinePowerShell implements IPipe {
|
|
public readonly name: string = 'inlinePowerShell';
|
|
|
|
public apply(code: string): string {
|
|
if (!code || !hasLines(code)) {
|
|
return code;
|
|
}
|
|
const processor = new Array<(data: string) => string>(...[ // for broken ESlint "indent"
|
|
inlineComments,
|
|
mergeLinesWithBacktick,
|
|
mergeHereStrings,
|
|
mergeNewLines,
|
|
]).reduce((a, b) => (data) => b(a(data)));
|
|
const newCode = processor(code);
|
|
return newCode;
|
|
}
|
|
}
|
|
|
|
function hasLines(text: string) {
|
|
return text.includes('\n') || text.includes('\r');
|
|
}
|
|
|
|
/*
|
|
Line comments using "#" are replaced with inline comment syntax <# comment.. #>
|
|
Otherwise single # comments out rest of the code
|
|
*/
|
|
function inlineComments(code: string): string {
|
|
const makeInlineComment = (comment: string) => {
|
|
const value = comment?.trim();
|
|
if (!value) {
|
|
return '<##>';
|
|
}
|
|
return `<# ${value} #>`;
|
|
};
|
|
return code.replaceAll(/<#.*?#>|#(.*)/g, (match, captureComment) => {
|
|
if (captureComment === undefined) {
|
|
return match;
|
|
}
|
|
return makeInlineComment(captureComment);
|
|
});
|
|
/*
|
|
Other alternatives considered:
|
|
--------------------------
|
|
/#(?<!<#)(?![<>])(.*)$/gm
|
|
-------------------------
|
|
✅ Simple, yet matches and captures only what's necessary
|
|
❌ Fails to match some cases
|
|
❌ `Write-Host "hi" # Comment ending line inline comment but not one #>`
|
|
❌ `Write-Host "hi" <#Comment starting like inline comment start but not one`
|
|
❌ `Write-Host "hi" #>Comment starting like inline comment end but not one`
|
|
❌ Uses lookbehind
|
|
Safari does not yet support lookbehind and syntax, leading application to not
|
|
load and throw "Invalid regular expression: invalid group specifier name"
|
|
https://caniuse.com/js-regexp-lookbehind
|
|
⏩ Usage
|
|
return code.replaceAll(/#(?<!<#)(?![<>])(.*)$/gm, (match, captureComment) => {
|
|
return makeInlineComment(captureComment)
|
|
});
|
|
----------------
|
|
/<#.*?#>|#(.*)/g
|
|
----------------
|
|
✅ Simple yet affective
|
|
❌ Matches all comments, but only captures dash comments
|
|
❌ Fails to match some cases
|
|
❌ `Write-Host "hi" # Comment ending line inline comment but not one #>`
|
|
❌ `Write-Host "hi" <#Comment starting like inline comment start but not one`
|
|
⏩ Usage
|
|
return code.replaceAll(/<#.*?#>|#(.*)/g, (match, captureComment) => {
|
|
if (captureComment === undefined) {
|
|
return match;
|
|
}
|
|
return makeInlineComment(captureComment);
|
|
});
|
|
------------------------------------
|
|
/(^(?:<#.*?#>|[^#])*)(?:(#)(.*))?/gm
|
|
------------------------------------
|
|
✅ Covers all cases
|
|
❌ Matches every line, three capture groups are used to build result
|
|
⏩ Usage
|
|
return code.replaceAll(/(^(?:<#.*?#>|[^#])*)(?:(#)(.*))?/gm,
|
|
(match, captureLeft, captureDash, captureComment) => {
|
|
if (!captureDash) {
|
|
return match;
|
|
}
|
|
return captureLeft + makeInlineComment(captureComment);
|
|
});
|
|
*/
|
|
}
|
|
|
|
function getLines(code: string): string[] {
|
|
return (code?.split(/\r\n|\r|\n/) || []);
|
|
}
|
|
|
|
/*
|
|
Merges inline here-strings to a single lined string with Windows line terminator (\r\n)
|
|
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules#here-strings
|
|
*/
|
|
function mergeHereStrings(code: string) {
|
|
const regex = /@(['"])\s*(?:\r\n|\r|\n)((.|\n|\r)+?)(\r\n|\r|\n)\1@/g;
|
|
return code.replaceAll(regex, (_$, quotes, scope) => {
|
|
const newString = getHereStringHandler(quotes);
|
|
const escaped = scope.replaceAll(quotes, newString.escapedQuotes);
|
|
const lines = getLines(escaped);
|
|
const inlined = lines.join(newString.separator);
|
|
const quoted = `${newString.quotesAround}${inlined}${newString.quotesAround}`;
|
|
return quoted;
|
|
});
|
|
}
|
|
interface IInlinedHereString {
|
|
readonly quotesAround: string;
|
|
readonly escapedQuotes: string;
|
|
readonly separator: string;
|
|
}
|
|
function getHereStringHandler(quotes: string): IInlinedHereString {
|
|
/*
|
|
We handle @' and @" differently.
|
|
Single quotes are interpreted literally and doubles are expandable.
|
|
*/
|
|
const expandableNewLine = '`r`n';
|
|
switch (quotes) {
|
|
case '\'':
|
|
return {
|
|
quotesAround: '\'',
|
|
escapedQuotes: '\'\'',
|
|
separator: `'+"${expandableNewLine}"+'`,
|
|
};
|
|
case '"':
|
|
return {
|
|
quotesAround: '"',
|
|
escapedQuotes: '`"',
|
|
separator: expandableNewLine,
|
|
};
|
|
default:
|
|
throw new Error(`expected quotes: ${quotes}`);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Input ->
|
|
Get-Service * `
|
|
Sort-Object StartType `
|
|
Format-Table Name, ServiceType, Status -AutoSize
|
|
Output ->
|
|
Get-Service * | Sort-Object StartType | Format-Table -AutoSize
|
|
*/
|
|
function mergeLinesWithBacktick(code: string) {
|
|
/*
|
|
The regex actually wraps any whitespace character after backtick and before newline
|
|
However, this is not always the case for PowerShell.
|
|
I see two behaviors:
|
|
1. If inside string, it's accepted (inside " or ')
|
|
2. If part of a command, PowerShell throws "An empty pipe element is not allowed"
|
|
However we don't need to be so robust and handle this complexity (yet), so for easier regex
|
|
we wrap it anyway
|
|
*/
|
|
return code.replaceAll(/ +`\s*(?:\r\n|\r|\n)\s*/g, ' ');
|
|
}
|
|
|
|
function mergeNewLines(code: string) {
|
|
return getLines(code)
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0)
|
|
.join('; ');
|
|
}
|