powershell/plugins/sdk-cs-simplifier.ts (140 lines of code) (raw):

/* eslint-disable prefer-const */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { AutorestExtensionHost as Host } from '@autorest/extension-base'; import { TrieNode } from '../utils/TrieNode'; const predifinedNamespaces: Set<string> = new Set<string>(['Newtonsoft.Json', 'Newtonsoft.Json.Converters', 'System.Collections', 'System.Collections.Generic', 'System.Net', 'System.Net.Http', 'System.Threading', 'System.Threading.Tasks', 'Microsoft.Rest.Serialization', 'System.IO', 'System.Runtime', 'System.Runtime.Serialization', 'Microsoft.Rest', 'Microsoft.Rest.Azure', 'System.Linq', 'Models']); const exceptionClassNames: Map<string, string> = new Map<string, string>([ ['JsonTransformation', 'Microsoft'], ['SafeJsonConvert', 'Microsoft'], ['Formatting', ''], ['DateFormatHandling', ''], ['DateTimeZoneHandling', ''], ['NullValueHandling', ''], ['ReferenceLoopHandling', ''], ['Headers', ''] ]); const root: TrieNode = initTrie(predifinedNamespaces); const usingRegex = /\n {4}using .*;/i; const namespaceRegex = /(?<=using )(.*)(?=[;])/i; //const thisRegex = /this\.(?![Client])/g; const thisRegex = /this\./g; const characterCheckRegex = /(?![a-zA-Z])/i; export async function simplifierPlugin(service: Host) { const files = await service.listInputs(); const trimTasks = files.map(async file => { let namespacesToAdd: Set<string> = new Set<string>(); let content: string = await service.readFile(file); const usings = findUsing(content, namespacesToAdd); const beforeUsings: string = content.substring(0, usings[0]); let afterUsings: string = content.substring(usings[1]); afterUsings = trimNamespace(afterUsings, namespacesToAdd); content = removeThis(addUsings(beforeUsings, afterUsings, sortNamespaces([...namespacesToAdd]).join(''))); service.writeFile({ filename: file, content: content, sourceMap: undefined, artifactType: 'source-file-csharp'}); }); await Promise.all(trimTasks); } function removeThis(content: string): string { return content.replace(thisRegex, ''); } function addUsings(beforeUsings: string, afterUsings: string, namespaces: string): string { return beforeUsings.concat(namespaces).concat(afterUsings); } function findUsing(content: string, namespacesToAdd: Set<string>): [number, number] { let index = 0; let start = 0; let end = 0; while (start != -1) { let segment: string = content.substring(index); start = segment.search(namespaceRegex); if (start != -1) { end = segment.indexOf(';', start); const namespace = segment.substring(start, end); findChildNamespace(namespace.split('.'), root, namespacesToAdd); index += end; } } return [content.search(usingRegex), index + 1]; } function sortNamespaces(namespaces: Array<string>): Array<string> { return namespaces.sort((a, b) => { const arrA: Array<string> = a.split(';')[0].split('.'); const arrB: Array<string> = b.split(';')[0].split('.'); const length: number = arrA.length < arrB.length ? arrA.length : arrB.length; for (let i = 0; i < length; i++) { if (arrA[i] < arrB[i]) { return -1; } else if (arrA[i] > arrB[i]) { return 1; } } return arrA.length - arrB.length; }); } function trimNamespace(content: string, namespacesToAdd: Set<string>): string { root.children.forEach((current: TrieNode, key: string) => { let start = 0; let end = 0; while (start != -1) { start = content.indexOf(key, start); if (start != -1 && start + key.length < content.length) { end = content.indexOf(' ', start + key.length); const namespaceToTrim = findChildNamespace(content.substring(start, end).split('.'), root, namespacesToAdd); if (namespaceToTrim != '') { content = trim(content, start, start + namespaceToTrim.length + 1); } else { start = start + key.length; } } } }); return content; } function findChildNamespace(segments: Array<string>, root: TrieNode, namespacesToAdd: Set<string>): string { let className: string = segments.pop()!; if (className.search(characterCheckRegex) != -1) { segments.push(className.substring(0, className.search(characterCheckRegex))); } let namespaceToTrim = ''; let currentNamespaces: Array<string> = new Array<string>(); let i = 0; for (; i < segments.length; i++) { let segment = segments[i]; if (!root.hasChild(segment)) { break; } root = root.getChild(segment)!; namespaceToTrim += namespaceToTrim == '' ? segment : ('.' + segment); if (predifinedNamespaces.has(namespaceToTrim)) { currentNamespaces.push(`\n using ${namespaceToTrim};`); } } if (!predifinedNamespaces.has(namespaceToTrim)) { return ''; } if (exceptionClassNames.has(segments[i])) { //can't handle this case, have to hard code 'SafeJsonConvert' if (segments[i] == 'SafeJsonConvert') { return exceptionClassNames.get(segments[i])!; } namespaceToTrim = exceptionClassNames.get(segments[i])!; } currentNamespaces.forEach(namespace => namespacesToAdd.add(namespace)); return namespaceToTrim; } function trim(content: string, start: number, end: number): string { return content.substring(0, start).concat(content.substring(end)); } function initTrie(predifinedNamespaces: Set<string>): TrieNode { const dummy: TrieNode = new TrieNode(''); predifinedNamespaces.forEach(namespace => { const names: Array<string> = namespace.split('.'); if (!dummy.hasChild(names[0])) { dummy.addChild(names[0]); } let current: TrieNode = dummy.getChild(names[0])!; for (let i = 1; i < names.length; i++) { if (!current.hasChild(names[i])) { current?.addChild(names[i]); } current = current.getChild(names[i])!; current.isEnd = true; } }); return dummy; }