Skip to content

Instantly share code, notes, and snippets.

@PhiLhoSoft
Last active June 27, 2024 09:47
Show Gist options
  • Save PhiLhoSoft/2854f64f758478129179648560d47b1d to your computer and use it in GitHub Desktop.
Save PhiLhoSoft/2854f64f758478129179648560d47b1d to your computer and use it in GitHub Desktop.
VSCode Macros (exceedsystem.vscode-macros) for Angular refactoring
const vscode = require('vscode');
/*
* VSCode Macros (exceedsystem.vscode-macros) for Angular refactoring.
*
* Macro configuration settings
* {
* [name: string]: { ... Name of the macro
* no: number, ... Order of the macro
* func: () => string | undefined ... Name of the body of the macro function; if returning a string, it is shown in a notification.
* }
* }
*/
module.exports.macroCommands = {
ConvertInjection: {
no: 1,
func: convertInjection,
},
};
/**
* Convert a classical Angular injection in a constructor to the modern version with inject().
*
* You have to move the line out of the constructor, as a field declaration, of course.
*/
function convertInjection() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
// Return an error message if necessary. For example when focus when running the macro isn't on an editor.
return 'Editor is not opening.';
}
const document = editor.document;
const line = document.lineAt(editor.selection.active.line);
// Would be cleaner to do a semantic analysis, but it would need an external library (?) and some knowledge yet to learn.
// So I use a regexp matching the most common patterns found in our code.
const changedLine = line.text.replace(
// Match the starting indentation, an optional injection token, an optional indicator of optional injection,
// the visibility keyword (mandatory, we work on injected fields only), the optional override keyword,
// the optional (but mandatory in our code!) readonly keyword,
// then the declared identifier itself, perhaps followed by a question mark if optional, and the type separator (colon),
// followed by the type, potentially with a generic type (simple only in our code), and a final comma.
/^(?<indentation>\s+)(?:@Inject\((?<token>\w+)\) )?(?<optional>@Optional\(\) )?(?<visibility>private|public|protected) ?(?<override>override)? ?(?<ro>readonly)? ?(?<identifier>\w+)(?<opt>\?)?: (?<type>\w+)(?<generic><\w+>)?,$/,
// The matched string, positional parameters (ignored, I don't want to rely on order alone), offset of match, the target (ie. line.text),
// and a Record with the capture names as keys (the "(?<name>" pattern) and the captured parts as values (undefined if optional & not found).
(match, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, offset, target, g) => {
// We reorder the parts, using the names, detecting missing optional parts.
return `${
g.indentation}${
// public being the default, we omit it at field / method declaration level.
g.visibility !== 'public' ? g.visibility + ' ' : ''}${
g.override ? 'override ' : ''}${g.ro ? 'readonly ' : ''}${
g.identifier}${
g.generic || g.token ? `: ${g.type}${g.generic ?? ''}${g.optional ? ' | null' : ''}` : ''} = inject(${
g.token ?? g.type}${
g.optional ? ', { optional: true }' : ''});`;
},
);
editor.edit((edit) => {
edit.replace(line.range, changedLine);
});
// Go to next line to allow quick chaining of these commands.
vscode.commands.executeCommand('cursorMove', { to: 'down', by: 'wrappedLine', value: 1 });
}
@PhiLhoSoft
Copy link
Author

/** Convert an assignment `this.foo = whatever;` to a signal update: `this.foo.set(whatever);`. */
function convertToSignal() {
	const editor = vscode.window.activeTextEditor;
	if (!editor) {
		// Return an error message if necessary. For example when focus when running the macro isn't on an editor.
		return 'Editor is not opening.';
	}
	const document = editor.document;
	const line = document.lineAt(editor.selection.active.line);
	const changedLine = line.text.replace(
		/^(?<indentation>\s+)this\.(?<identifier>[\w$]+) = (?<rightPart>.+);$/,
		// The matched string, positional parameters (ignored, I don't want to rely on order alone), offset of match, the target (ie. line.text),
		// and a Record with the capture names as keys (the "(?<name>" pattern) and the captured parts as values (undefined if optional & not found).
		(match, _1, _2, _3, offset, target, g) => {
			return `${g.indentation}this.${g.identifier}.set(${g.rightPart});`;
		},
	);
	editor.edit((edit) => {
		edit.replace(line.range, changedLine);
	});
}

@PhiLhoSoft
Copy link
Author

PhiLhoSoft commented Jun 27, 2024

/** Convert `styleUrls: ['one-item'],` to `styleUrl: 'one-item',`. */
function convertStyleUrls() {
	const editor = vscode.window.activeTextEditor;
	if (!editor) {
		// Return an error message if necessary. For example when focus when running the macro isn't on an editor.
		return 'Editor is not opening.';
	}
	const document = editor.document;
	const line = document.lineAt(editor.selection.active.line);
	const changedLine = line.text.replace(
		/^(?<indentation>\s+)styleUrls: \['(?<path>[\w./-]+)'\],$/,
		(match, _1, _2, offset, target, g) => {
			return `${g.indentation}styleUrl: '${g.path}',`;
		},
	);
	editor.edit((edit) => {
		edit.replace(line.range, changedLine);
	});
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment