• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-cli / 7141295157

08 Dec 2023 12:25PM UTC coverage: 66.277% (-4.1%) from 70.414%
7141295157

push

github

web-flow
feat(igr-ts): add igr-ts proj type (#1146)

* switch to react functions initial commit

* fix(igr-es6): switch to functions in all views

* feat(igr-es6): add igr-ts-es6 proj type

* fix(igr-ts-es6): update react router version and style import

* Revert "fix(igr-es6): switch to functions in all views"

This reverts commit ab678d1e8.

* Revert "switch to react functions initial commit"

This reverts commit 338bd1ed1.

* chore(*): fix template names

* chore(*): fix comment

* fix(igr-ts-es6): configure tests

* chore(*): remove %PUBLIC_URL% from index.html

* chore(*): make app test more generic

* chore(*): update test config and clean leftovers

* chore(*): uncomment groups logic

* chore(*): add config for igr-ts-es6 process

* chore(*): add groups.json

* feat(igr-ts-es6): add base with home template + minor chanegs

* feat(igr-ts-es6): add view to top-nav

* chore(*): update readme

* chore: release 12.0.6-alpha.0

* feat(react): add base template

* fix(react): igr-ts-es6 -> ig-ts

* chore(*): minor change in React template

* chore: release 12.0.6-alpha.1

* chore: update react grid to latest #1147

* chore: release 12.0.6-beta.0

* chore: add force to install commands

* feat(igr-ts): add deprecated to old react

* chore: remove serviceWorker

* chore: update packages

* chore: formatting

* chore: remove asyncComponent

* chore: fix lint

* chore: add asyncComponent to top-nav

* chore: remove unused imports

* fix: search for empty template insted of first one

* chore: fix lint

* chore: unused import

* chore: unused imports

* chore: release v12.0.6-beta.1

* release: 12.1.0-beta.0

* chore: set default port #1153

* chore: fix lint and remove .env file

* chore: remove unused imports

* feat: upgrade to react router 6

* chore: release 12.1.0-beta.1

* chore: fix lint

* chore: rena... (continued)

354 of 561 branches covered (0.0%)

Branch coverage included in aggregate %.

17 of 371 new or added lines in 31 files covered. (4.58%)

2 existing lines in 1 file now uncovered.

3842 of 5770 relevant lines covered (66.59%)

79.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/packages/cli/templates/react/ReactTypeScriptFileUpdate.ts
NEW
1
import { App, FS_TOKEN, IFileSystem, TypeScriptUtils, Util } from "@igniteui/cli-core";
×
NEW
2
import * as ts from "typescript";
×
3

NEW
4
const DEFAULT_ROUTES_VARIABLE = "routes";
×
5
/**
6
 * Apply various updates to typescript files using AST
7
 */
NEW
8
export class ReactTypeScriptFileUpdate {
×
9

NEW
10
        protected formatOptions = { spaces: false, indentSize: 4, singleQuotes: false };
×
11
        private fileSystem: IFileSystem;
12
        private targetSource: ts.SourceFile;
13
        private importsMeta: { lastIndex: number, modulePaths: string[] };
14

15
        private requestedImports: Array<{ as: string | undefined, from: string, component: string, edit: boolean }>;
16

17
        private createdStringLiterals: string[];
18

19
        /** Create updates for a file. Use `add<X>` methods to add transformations and `finalize` to apply and save them. */
NEW
20
        constructor(private targetPath: string) {
×
NEW
21
                this.fileSystem = App.container.get<IFileSystem>(FS_TOKEN);
×
NEW
22
                this.initState();
×
23
        }
24

25
        /** Applies accumulated transforms, saves and formats the file */
26
        public finalize() {
27
                // add new import statements after visitor walks:
NEW
28
                this.addNewFileImports();
×
29

NEW
30
                TypeScriptUtils.saveFile(this.targetPath, this.targetSource);
×
NEW
31
                this.formatFile(this.targetPath);
×
32
                // reset state in case of further updates
NEW
33
                this.initState();
×
34
        }
35

36
        public addRoute(
37
                path: string,
38
                component: string,
39
                name: string,
40
                filePath: string,
41
                routerChildren: string,
42
                importAlias: string,
43
                routesVariable = DEFAULT_ROUTES_VARIABLE
×
44
        ) {
NEW
45
                this.addRouteModuleEntry(path, component, name, filePath, routerChildren, importAlias, routesVariable);
×
46
        }
47

48
        //#region File state
49

50
        /** Initializes existing imports info, [re]sets import and `NgModule` edits */
51
        protected initState() {
NEW
52
                this.targetSource = TypeScriptUtils.getFileSource(this.targetPath);
×
NEW
53
                this.importsMeta = this.loadImportsMeta();
×
NEW
54
                this.requestedImports = [];
×
NEW
55
                this.createdStringLiterals = [];
×
56
        }
57

58
        /* load some metadata about imports */
59
        protected loadImportsMeta() {
NEW
60
                const meta = { lastIndex: 0, modulePaths: [] };
×
61

NEW
62
                for (let i = 0; i < this.targetSource.statements.length; i++) {
×
NEW
63
                        const statement = this.targetSource.statements[i];
×
64
                        switch (statement.kind) {
65
                                case ts.SyntaxKind.ImportDeclaration:
NEW
66
                                        const importStmt = (statement as ts.ImportDeclaration);
×
67

68
                                        if (importStmt.importClause && importStmt.importClause.namedBindings &&
×
69
                                                importStmt.importClause.namedBindings.kind !== ts.SyntaxKind.NamespaceImport) {
70
                                                // don't add imports without named (e.g. `import $ from "JQuery"` or `import "./my-module.js";`)
71
                                                // don't add namespace imports (`import * as fs`) as available for editing, maybe in the future
NEW
72
                                                meta.modulePaths.push((importStmt.moduleSpecifier as ts.StringLiteral).text);
×
73
                                        }
74

75
                                // don't add equals imports (`import url = require("url")`) as available for editing, maybe in the future
76
                                case ts.SyntaxKind.ImportEqualsDeclaration:
×
NEW
77
                                        meta.lastIndex = i + 1;
×
NEW
78
                                        break;
×
79
                                default:
NEW
80
                                        break;
×
81
                        }
82
                }
83

NEW
84
                return meta;
×
85
        }
86

87
        //#endregion File state
88

89
        protected addRouteModuleEntry(
90
                path: string,
91
                component: string,
92
                name: string,
93
                filePath: string,
94
                routerChildren: string,
95
                importAlias: string,
96
                routesVariable = DEFAULT_ROUTES_VARIABLE
×
97
        ) {
NEW
98
                const isRouting: boolean = path.indexOf("routes") >= 0;
×
99

100
                if (isRouting && this.targetSource.text.indexOf(path.slice(0, -4)) > 0) {
×
NEW
101
                        return;
×
102
                }
103

104
                if (path) {
NEW
105
                        const relativePath: string = Util.relativePath(this.targetPath, filePath, true, true);
×
106

NEW
107
                        this.requestImport(relativePath, importAlias, component);
×
108
                }
109

110
                // https://github.com/Microsoft/TypeScript/issues/14419#issuecomment-307256171
NEW
111
                const transformer: ts.TransformerFactory<ts.Node> = <T extends ts.Node>(context: ts.TransformationContext) =>
×
NEW
112
                        (rootNode: T) => {
×
113
                                // the visitor that should be used when adding routes to the main route array
NEW
114
                                const conditionalVisitor: ts.Visitor = (node: ts.Node): ts.Node => {
×
115
                                        if (node.kind === ts.SyntaxKind.ArrayLiteralExpression) {
NEW
116
                                                const newObject = this.createRouteEntry(path, component, name, routerChildren);
×
NEW
117
                                                const array = (node as ts.ArrayLiteralExpression);
×
NEW
118
                                                this.createdStringLiterals.push(path, name);
×
NEW
119
                                                const notFoundWildCard = ".*";
×
NEW
120
                                                const nodes = ts.visitNodes(array.elements, visitor);
×
NEW
121
                                                const errorRouteNode = nodes.filter(element => element.getText().includes(notFoundWildCard))[0];
×
NEW
122
                                                let resultNodes = null;
×
123
                                                if (errorRouteNode) {
NEW
124
                                                        resultNodes = nodes
×
125
                                                                .slice(0, nodes.indexOf(errorRouteNode))
126
                                                                .concat(newObject)
127
                                                                .concat(errorRouteNode);
128
                                                } else {
NEW
129
                                                        resultNodes = nodes
×
130
                                                                .concat(newObject);
131
                                                }
132

NEW
133
                                                const elements = ts.factory.createNodeArray([
×
134
                                                        ...resultNodes
135
                                                ]);
136

NEW
137
                                                return ts.factory.updateArrayLiteralExpression(array, elements);
×
138
                                        } else {
NEW
139
                                                return ts.visitEachChild(node, conditionalVisitor, context);
×
140
                                        }
141
                                };
142

143
                                let visitCondition;
144

145
                                if (!isRouting) {
NEW
146
                                        visitCondition = (node: ts.Node): boolean => {
×
NEW
147
                                                return node.kind === ts.SyntaxKind.VariableDeclaration &&
×
148
                                                        (node as ts.VariableDeclaration).name.getText() === routesVariable;
149
                                                        // no type currently
150
                                                        //(node as ts.VariableDeclaration).type.getText() === "Route[]";
151
                                        };
152
                                } else {
NEW
153
                                        visitCondition = (node: ts.Node): boolean => {
×
NEW
154
                                                return undefined;
×
155
                                        };
156
                                }
157

NEW
158
                                const visitor: ts.Visitor = this.createVisitor(conditionalVisitor, visitCondition, context);
×
NEW
159
                                context.enableSubstitution(ts.SyntaxKind.ClassDeclaration);
×
NEW
160
                                return ts.visitNode(rootNode, visitor);
×
161
                        };
162

NEW
163
                this.targetSource = ts.transform(this.targetSource, [transformer], {
×
164
                        pretty: true // oh well..
165
                }).transformed[0] as ts.SourceFile;
166

NEW
167
                this.finalize();
×
168
        }
169

170
        protected requestImport(modulePath: string, routerAlias: string, componentName: string) {
NEW
171
                const existing = this.requestedImports.find(x => x.from === modulePath);
×
172
                if (!existing) {
173
                        // new imports, check if already exists in file
NEW
174
                        this.requestedImports.push({
×
175
                                as: routerAlias,
176
                                from: modulePath,
177
                                component: componentName,
178
                                edit: this.importsMeta.modulePaths.indexOf(modulePath) !== -1
179
                        });
180
                } else {
NEW
181
                        return;
×
182
                }
183
        }
184

185
        /** Add `import` statements not previously found in the file  */
186
        protected addNewFileImports() {
NEW
187
                const newImports = this.requestedImports.filter(x => !x.edit);
×
188
                if (!newImports.length) {
NEW
189
                        return;
×
190
                }
191

NEW
192
                const newStatements = ts.factory.createNodeArray([
×
193
                        ...this.targetSource.statements.slice(0, this.importsMeta.lastIndex),
NEW
194
                        ...newImports.map(x => this.createIdentifierImport(x.from, x.as, x.component)),
×
195
                        ...this.targetSource.statements.slice(this.importsMeta.lastIndex)
196
                ]);
NEW
197
                newImports.forEach(x => this.createdStringLiterals.push(x.from));
×
198

NEW
199
                this.targetSource = ts.factory.updateSourceFile(this.targetSource, newStatements);
×
200
        }
201

202
        protected createIdentifierImport(importPath: string, as: string, component: string): ts.ImportDeclaration {
203
                let exportedObject: string | undefined;
204
                let exportedObjectName: string | undefined;
205
                let importClause: ts.ImportClause | undefined;
206
                if (as) {
NEW
207
                        exportedObject = "routes";
×
NEW
208
                        exportedObjectName = as.replace(/\s/g, "");
×
NEW
209
                        importClause = ts.factory.createImportClause(
×
210
                                false,
211
                                undefined,
212
                                ts.factory.createNamedImports([
213
                                        ts.factory.createImportSpecifier(false, ts.factory.createIdentifier(exportedObject),
214
                                                ts.factory.createIdentifier(exportedObjectName))
215
                                ])
216
                        );
217
                } else {
NEW
218
                        importClause = ts.factory.createImportClause(
×
219
                                false,
220
                                ts.factory.createIdentifier(component),
221
                                undefined
222
                        );
223
                }
NEW
224
                const importDeclaration = ts.factory.createImportDeclaration(
×
225
                        undefined,
226
                        undefined,
227
                        importClause,
228
                        ts.factory.createStringLiteral(importPath, true));
NEW
229
                return importDeclaration;
×
230
        }
231

232
        //#region ts.TransformerFactory
233

234
        /** Transformation to apply edits to existing named import declarations */
NEW
235
        protected importsTransformer: ts.TransformerFactory<ts.Node> =
×
NEW
236
                <T extends ts.Node>(context: ts.TransformationContext) => (rootNode: T) => {
×
NEW
237
                        const editImports = this.requestedImports.filter(x => x.edit);
×
238

239
                        // https://github.com/Microsoft/TypeScript/issues/14419#issuecomment-307256171
NEW
240
                        const visitor = (node: ts.Node): ts.Node => {
×
241
                                if (node.kind === ts.SyntaxKind.ImportDeclaration &&
×
NEW
242
                                        editImports.find(x => x.from === ((node as ts.ImportDeclaration).moduleSpecifier as ts.StringLiteral).text)
×
243
                                ) {
244
                                        // visit just the source file main array (second visit)
NEW
245
                                        return visitImport(node as ts.ImportDeclaration);
×
246
                                } else {
NEW
247
                                        node = ts.visitEachChild(node, visitor, context);
×
248
                                }
NEW
249
                                return node;
×
250
                        };
251
                        function visitImport(node: ts.Node) {
NEW
252
                                node = ts.visitEachChild(node, visitImport, context);
×
NEW
253
                                return node;
×
254
                        }
NEW
255
                        return ts.visitNode(rootNode, visitor);
×
256
                }
257

258
        //#endregion ts.TransformerFactory
259

260
        //#region Formatting
261

262
        /** Format a TS source file, very TBD */
263
        protected formatFile(filePath: string) {
264
                // formatting via LanguageService https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
265
                // https://github.com/Microsoft/TypeScript/issues/1651
266

NEW
267
                let text = this.fileSystem.readFile(filePath);
×
268
                // create the language service files
NEW
269
                const services = ts.createLanguageService(this.getLanguageHost(filePath), ts.createDocumentRegistry());
×
270

NEW
271
                this.readFormatConfigs();
×
NEW
272
                const textChanges = services.getFormattingEditsForDocument(filePath, this.getFormattingOptions());
×
NEW
273
                text = this.applyChanges(text, textChanges);
×
274

275
                if (this.formatOptions.singleQuotes) {
276
                        for (const str of this.createdStringLiterals) {
277
                                // there shouldn't be duplicate strings of these
NEW
278
                                text = text.replace(`"${str}"`, `'${str}'`);
×
279
                        }
280
                }
281

NEW
282
                this.fileSystem.writeFile(filePath, text);
×
283
        }
284

285
        /**  Try and parse formatting from project `.editorconfig` / `tslint.json` */
286
        protected readFormatConfigs() {
287
                if (this.fileSystem.fileExists(".editorconfig")) {
288
                        // very basic parsing support
NEW
289
                        const text = this.fileSystem.readFile(".editorconfig", "utf-8");
×
NEW
290
                        const options = text
×
291
                                .replace(/\s*[#;].*([\r\n])/g, "$1") //remove comments
292
                                .replace(/\[(?!\*\]|\*.ts).+\][^\[]+/g, "") // leave [*]/[*.ts] sections
293
                                .split(/\r\n|\r|\n/)
294
                                .reduce((obj, x) => {
295
                                        if (x.indexOf("=") !== -1) {
NEW
296
                                                const pair = x.split("=");
×
NEW
297
                                                obj[pair[0].trim()] = pair[1].trim();
×
298
                                        }
NEW
299
                                        return obj;
×
300
                                }, {});
301

NEW
302
                        this.formatOptions.spaces = options["indent_style"] === "space";
×
303
                        if (options["indent_size"]) {
NEW
304
                                this.formatOptions.indentSize = parseInt(options["indent_size"], 10) || this.formatOptions.indentSize;
×
305
                        }
306

307
                        if (options["quote_type"]) {
NEW
308
                                this.formatOptions.singleQuotes = options["quote_type"] === "single";
×
309
                        }
310
                }
311
                if (this.fileSystem.fileExists("tslint.json")) {
312
                        // tslint prio - overrides other settings
NEW
313
                        const options = JSON.parse(this.fileSystem.readFile("tslint.json", "utf-8"));
×
314
                        if (options.rules && options.rules.indent && options.rules.indent[0]) {
×
NEW
315
                                this.formatOptions.spaces = options.rules.indent[1] === "spaces";
×
316
                                if (options.rules.indent[2]) {
NEW
317
                                        this.formatOptions.indentSize = parseInt(options.rules.indent[2], 10);
×
318
                                }
319
                        }
320
                        if (options.rules && options.rules.quotemark && options.rules.quotemark[0]) {
×
NEW
321
                                this.formatOptions.singleQuotes = options.rules.quotemark.indexOf("single") !== -1;
×
322
                        }
323
                }
324
        }
325

326
        /**
327
         * Apply formatting changes (position based) in reverse
328
         * from https://github.com/Microsoft/TypeScript/issues/1651#issuecomment-69877863
329
         */
330
        private applyChanges(orig: string, changes: ts.TextChange[]): string {
NEW
331
                let result = orig;
×
NEW
332
                for (let i = changes.length - 1; i >= 0; i--) {
×
NEW
333
                        const change = changes[i];
×
NEW
334
                        const head = result.slice(0, change.span.start);
×
NEW
335
                        const tail = result.slice(change.span.start + change.span.length);
×
NEW
336
                        result = head + change.newText + tail;
×
337
                }
NEW
338
                return result;
×
339
        }
340

341
        /** Return source file formatting options */
342
        private getFormattingOptions(): ts.FormatCodeSettings {
343
                const formatOptions: ts.FormatCodeSettings = {
344
                        // tslint:disable:object-literal-sort-keys
345
                        indentSize: this.formatOptions.indentSize,
346
                        tabSize: 4,
347
                        newLineCharacter: ts.sys.newLine,
348
                        convertTabsToSpaces: this.formatOptions.spaces,
349
                        indentStyle: ts.IndentStyle.Smart,
350
                        insertSpaceAfterCommaDelimiter: true,
351
                        insertSpaceAfterSemicolonInForStatements: true,
352
                        insertSpaceBeforeAndAfterBinaryOperators: true,
353
                        insertSpaceAfterKeywordsInControlFlowStatements: true,
354
                        insertSpaceAfterTypeAssertion: true
355
                        // tslint:enable:object-literal-sort-keys
356
                };
357

NEW
358
                return formatOptions;
×
359
        }
360

361
        /** Get language service host, sloppily */
362
        private getLanguageHost(filePath: string): ts.LanguageServiceHost {
NEW
363
                const files = {};
×
NEW
364
                files[filePath] = { version: 0 };
×
365
                // create the language service host to allow the LS to communicate with the host
366
                const servicesHost: ts.LanguageServiceHost = {
NEW
367
                        getCompilationSettings: () => ({}),
×
NEW
368
                        getScriptFileNames: () => Object.keys(files),
×
NEW
369
                        getScriptVersion: fileName => files[fileName] && files[fileName].version.toString(),
×
370
                        getScriptSnapshot: fileName => {
371
                                if (!this.fileSystem.fileExists(fileName)) {
NEW
372
                                        return undefined;
×
373
                                }
NEW
374
                                return ts.ScriptSnapshot.fromString(this.fileSystem.readFile(fileName));
×
375
                        },
NEW
376
                        getCurrentDirectory: () => process.cwd(),
×
NEW
377
                        getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
×
378
                        readDirectory: ts.sys.readDirectory,
379
                        readFile: ts.sys.readFile,
380
                        fileExists: ts.sys.fileExists
381
                };
NEW
382
                return servicesHost;
×
383
        }
384

385
        //#endregion Formatting
386

387
        private createVisitor(
388
                conditionalVisitor: ts.Visitor,
389
                visitCondition: (node: ts.Node) => boolean,
390
                nodeContext: ts.TransformationContext
391
        ): ts.Visitor {
NEW
392
                return function visitor(node: ts.Node): ts.Node {
×
393
                        if (visitCondition(node)) {
NEW
394
                                node = ts.visitEachChild(node, conditionalVisitor, nodeContext);
×
395
                        } else {
NEW
396
                                node = ts.visitEachChild(node, visitor, nodeContext);
×
397
                        }
NEW
398
                        return node;
×
399
                };
400
        }
401

402
        private createRouteEntry(
403
                path: string,
404
                component: string,
405
                name: string,
406
                routerAlias: string
407
        ): ts.ObjectLiteralExpression {
NEW
408
                const routePath = ts.factory.createPropertyAssignment("path", ts.factory.createStringLiteral(path, true));
×
NEW
409
                const jsxElement = ts.factory.createJsxSelfClosingElement(
×
410
                        ts.factory.createIdentifier(component), [], undefined
411
                );
412
                const routeComponent =
NEW
413
                        ts.factory.createPropertyAssignment("element", jsxElement);
×
NEW
414
                const routeData = ts.factory.createPropertyAssignment("text", ts.factory.createStringLiteral(name, true));
×
415
                if (routerAlias) {
NEW
416
                        const childrenData = ts.factory.createPropertyAssignment("children", ts.factory.createIdentifier(routerAlias));
×
NEW
417
                        return ts.factory.createObjectLiteralExpression([routePath, routeComponent, routeData, childrenData]);
×
418
                } else {
NEW
419
                        return ts.factory.createObjectLiteralExpression([routePath, routeComponent, routeData]);
×
420
                }
421
        }
422
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc