igniteui-angular 22.2.0-rc.0 → 22.2.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/button-group/README.md +42 -9
- package/calendar/README.md +30 -20
- package/card/README.md +1 -1
- package/fesm2022/igniteui-angular-accordion.mjs +7 -7
- package/fesm2022/igniteui-angular-action-strip.mjs +11 -22
- package/fesm2022/igniteui-angular-action-strip.mjs.map +1 -1
- package/fesm2022/igniteui-angular-avatar.mjs +7 -7
- package/fesm2022/igniteui-angular-badge.mjs +7 -7
- package/fesm2022/igniteui-angular-banner.mjs +10 -10
- package/fesm2022/igniteui-angular-bottom-nav.mjs +22 -22
- package/fesm2022/igniteui-angular-button-group.mjs +32 -35
- package/fesm2022/igniteui-angular-button-group.mjs.map +1 -1
- package/fesm2022/igniteui-angular-calendar.mjs +132 -180
- package/fesm2022/igniteui-angular-calendar.mjs.map +1 -1
- package/fesm2022/igniteui-angular-card.mjs +47 -58
- package/fesm2022/igniteui-angular-card.mjs.map +1 -1
- package/fesm2022/igniteui-angular-carousel.mjs +22 -22
- package/fesm2022/igniteui-angular-chat-extras.mjs +6 -6
- package/fesm2022/igniteui-angular-chat.mjs +12 -12
- package/fesm2022/igniteui-angular-checkbox.mjs +7 -7
- package/fesm2022/igniteui-angular-chips.mjs +10 -10
- package/fesm2022/igniteui-angular-combo.mjs +69 -67
- package/fesm2022/igniteui-angular-combo.mjs.map +1 -1
- package/fesm2022/igniteui-angular-core.mjs +139 -92
- package/fesm2022/igniteui-angular-core.mjs.map +1 -1
- package/fesm2022/igniteui-angular-date-picker.mjs +53 -70
- package/fesm2022/igniteui-angular-date-picker.mjs.map +1 -1
- package/fesm2022/igniteui-angular-dialog.mjs +13 -13
- package/fesm2022/igniteui-angular-directives.mjs +194 -194
- package/fesm2022/igniteui-angular-drop-down.mjs +29 -29
- package/fesm2022/igniteui-angular-expansion-panel.mjs +28 -28
- package/fesm2022/igniteui-angular-grids-core.mjs +737 -661
- package/fesm2022/igniteui-angular-grids-core.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-grid.mjs +49 -49
- package/fesm2022/igniteui-angular-grids-hierarchical-grid.mjs +37 -37
- package/fesm2022/igniteui-angular-grids-lite.mjs +25 -17
- package/fesm2022/igniteui-angular-grids-lite.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-pivot-grid.mjs +80 -80
- package/fesm2022/igniteui-angular-grids-pivot-grid.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-tree-grid.mjs +55 -55
- package/fesm2022/igniteui-angular-icon.mjs +10 -10
- package/fesm2022/igniteui-angular-input-group.mjs +59 -55
- package/fesm2022/igniteui-angular-input-group.mjs.map +1 -1
- package/fesm2022/igniteui-angular-list.mjs +40 -40
- package/fesm2022/igniteui-angular-navbar.mjs +13 -13
- package/fesm2022/igniteui-angular-navigation-drawer.mjs +43 -38
- package/fesm2022/igniteui-angular-navigation-drawer.mjs.map +1 -1
- package/fesm2022/igniteui-angular-paginator.mjs +19 -19
- package/fesm2022/igniteui-angular-progressbar.mjs +19 -19
- package/fesm2022/igniteui-angular-query-builder.mjs +22 -22
- package/fesm2022/igniteui-angular-radio.mjs +25 -21
- package/fesm2022/igniteui-angular-radio.mjs.map +1 -1
- package/fesm2022/igniteui-angular-select.mjs +29 -33
- package/fesm2022/igniteui-angular-select.mjs.map +1 -1
- package/fesm2022/igniteui-angular-simple-combo.mjs +9 -9
- package/fesm2022/igniteui-angular-simple-combo.mjs.map +1 -1
- package/fesm2022/igniteui-angular-slider.mjs +28 -28
- package/fesm2022/igniteui-angular-snackbar.mjs +7 -7
- package/fesm2022/igniteui-angular-splitter.mjs +13 -13
- package/fesm2022/igniteui-angular-stepper.mjs +34 -34
- package/fesm2022/igniteui-angular-switch.mjs +7 -7
- package/fesm2022/igniteui-angular-tabs.mjs +34 -34
- package/fesm2022/igniteui-angular-time-picker.mjs +26 -36
- package/fesm2022/igniteui-angular-time-picker.mjs.map +1 -1
- package/fesm2022/igniteui-angular-toast.mjs +7 -7
- package/fesm2022/igniteui-angular-tree.mjs +28 -28
- package/fesm2022/igniteui-angular-virtual-scroll.mjs +497 -145
- package/fesm2022/igniteui-angular-virtual-scroll.mjs.map +1 -1
- package/migrations/common/UpdateChanges.d.ts +56 -0
- package/migrations/common/UpdateChanges.js +366 -38
- package/migrations/common/UpdateChanges.spec.js +829 -0
- package/migrations/migration-collection.json +1 -1
- package/migrations/update-22_2_0/index.js +145 -0
- package/migrations/update-22_2_0/index.spec.js +206 -0
- package/navigation-drawer/README.md +1 -1
- package/package.json +3 -3
- package/schematics/tsconfig.tsbuildinfo +1 -1
- package/skills/igniteui-angular-components/SKILL.md +9 -5
- package/skills/igniteui-angular-components/references/form-controls.md +1 -1
- package/skills/igniteui-angular-components/references/mcp-setup.md +14 -2
- package/skills/igniteui-angular-figma-to-app/SKILL.md +112 -525
- package/skills/igniteui-angular-figma-to-app/references/asset-extraction.md +49 -75
- package/skills/igniteui-angular-figma-to-app/references/design-provenance.md +201 -0
- package/skills/igniteui-angular-figma-to-app/references/design-token-bridge.md +175 -52
- package/skills/igniteui-angular-figma-to-app/references/figma-component-map.md +153 -99
- package/skills/igniteui-angular-figma-to-app/references/figma-exploration.md +226 -0
- package/skills/igniteui-angular-figma-to-app/references/mcp-setup.md +71 -105
- package/skills/igniteui-angular-figma-to-app/references/project-setup.md +63 -0
- package/skills/igniteui-angular-figma-to-app/references/theme-generation.md +120 -0
- package/skills/igniteui-angular-figma-to-app/references/validation-patterns.md +48 -50
- package/skills/igniteui-angular-generate-from-image-design/SKILL.md +11 -7
- package/skills/igniteui-angular-grids/SKILL.md +7 -3
- package/skills/igniteui-angular-grids/references/editing.md +1 -2
- package/skills/igniteui-angular-grids/references/grid-migration.md +1 -1
- package/skills/igniteui-angular-theming/SKILL.md +9 -5
- package/skills/igniteui-angular-theming/references/mcp-setup.md +12 -2
- package/types/igniteui-angular-button-group.d.ts +49 -34
- package/types/igniteui-angular-calendar.d.ts +33 -50
- package/types/igniteui-angular-card.d.ts +12 -17
- package/types/igniteui-angular-combo.d.ts +6 -0
- package/types/igniteui-angular-core.d.ts +25 -8
- package/types/igniteui-angular-grids-core.d.ts +48 -4
- package/types/igniteui-angular-grids-lite.d.ts +5 -1
- package/types/igniteui-angular-grids-pivot-grid.d.ts +1 -1
- package/types/igniteui-angular-input-group.d.ts +17 -3
- package/types/igniteui-angular-navigation-drawer.d.ts +1 -0
- package/types/igniteui-angular-radio.d.ts +5 -0
- package/types/igniteui-angular-time-picker.d.ts +0 -1
- package/types/igniteui-angular-virtual-scroll.d.ts +41 -13
- package/virtual-scroll/README.md +32 -1
|
@@ -47,6 +47,9 @@ const tsUtils_1 = require("./tsUtils");
|
|
|
47
47
|
const util_1 = require("./util");
|
|
48
48
|
const project_service_container_1 = require("./project-service-container");
|
|
49
49
|
const TSCONFIG_PATH = 'tsconfig.json';
|
|
50
|
+
const URL_TOKEN = 'url(';
|
|
51
|
+
/** Dependency and build output folders never searched for stylesheets. */
|
|
52
|
+
const SKIPPED_STYLE_DIRS = ['node_modules', 'dist', '.angular', '.git'];
|
|
50
53
|
var InputPropertyType;
|
|
51
54
|
(function (InputPropertyType) {
|
|
52
55
|
InputPropertyType["EVAL"] = "eval";
|
|
@@ -100,17 +103,25 @@ class UpdateChanges {
|
|
|
100
103
|
/** Sass (both .scss and .sass) files in the project being updated. */
|
|
101
104
|
get sassFiles() {
|
|
102
105
|
if (!this._sassFiles.length) {
|
|
103
|
-
//
|
|
106
|
+
// stylesheets can sit anywhere in the workspace, not only under sourceRoot
|
|
104
107
|
// also ignore schematics `styleext` as Sass can be used regardless
|
|
105
|
-
|
|
106
|
-
this.sourceDirsVisitor((fulPath, entry) => {
|
|
107
|
-
if (fulPath.endsWith('.scss') || fulPath.endsWith('.sass')) {
|
|
108
|
-
this._sassFiles.push(entry.path);
|
|
109
|
-
}
|
|
110
|
-
}, sourceDirs);
|
|
108
|
+
this.collectSassFiles(this.host.getDir('/'), this._sassFiles);
|
|
111
109
|
}
|
|
112
110
|
return this._sassFiles;
|
|
113
111
|
}
|
|
112
|
+
/** Collects the Sass files under a folder, skipping dependency and build output folders. */
|
|
113
|
+
collectSassFiles(dir, files) {
|
|
114
|
+
for (const name of dir.subfiles) {
|
|
115
|
+
if (name.endsWith('.scss') || name.endsWith('.sass')) {
|
|
116
|
+
files.push(dir.file(name).path);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const name of dir.subdirs) {
|
|
120
|
+
if (!SKIPPED_STYLE_DIRS.includes(name)) {
|
|
121
|
+
this.collectSassFiles(dir.dir(name), files);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
114
125
|
get service() {
|
|
115
126
|
if (!this._service) {
|
|
116
127
|
this._service = (0, tsUtils_1.getLanguageService)(this.tsFiles, this.host);
|
|
@@ -361,52 +372,361 @@ class UpdateChanges {
|
|
|
361
372
|
}
|
|
362
373
|
}
|
|
363
374
|
updateThemeProps(entryPath) {
|
|
364
|
-
var _a;
|
|
365
375
|
let fileContent = this.host.read(entryPath).toString();
|
|
366
376
|
let overwrite = false;
|
|
367
377
|
for (const change of this.themeChanges.changes) {
|
|
368
378
|
if (change.type !== schema_1.ThemeType.Property) {
|
|
369
379
|
continue;
|
|
370
380
|
}
|
|
371
|
-
if (fileContent.indexOf(change.owner)
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
381
|
+
if (fileContent.indexOf(change.owner) === -1) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
/** owner-func:( * ) */
|
|
385
|
+
const calls = this.findFunctionCalls(fileContent, change.owner, entryPath.endsWith('.sass'));
|
|
386
|
+
// rewrite back to front so the collected indices stay valid
|
|
387
|
+
for (const call of calls.reverse()) {
|
|
388
|
+
const rawBody = fileContent.substring(call.bodyStart, call.bodyEnd);
|
|
389
|
+
if (rawBody.indexOf(change.name) === -1) {
|
|
376
390
|
continue;
|
|
377
391
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
392
|
+
const name = (0, util_1.escapeRegExp)(change.name);
|
|
393
|
+
const replaceWith = change.replaceWith ? (0, util_1.escapeRegExp)(change.replaceWith) : undefined;
|
|
394
|
+
// both are matched at the argument's code start; Sass allows whitespace before the colon
|
|
395
|
+
const reg = new RegExp(String.raw `^${name}\s*:`);
|
|
396
|
+
const existing = new RegExp(String.raw `^${replaceWith}\s*:`);
|
|
397
|
+
// keep whatever sits in front of the closing bracket so the formatting is preserved
|
|
398
|
+
const trailing = /\s*$/.exec(rawBody).pop();
|
|
399
|
+
const body = rawBody.substring(0, rawBody.length - trailing.length);
|
|
400
|
+
const eol = fileContent.includes('\r\n') ? '\r\n' : '\n';
|
|
401
|
+
const params = this.splitFunctionProps(body);
|
|
402
|
+
const codeStarts = params.map(param => this.leadingCommentsEnd(param));
|
|
403
|
+
// renaming onto a name the call already passes would make it a duplicate argument
|
|
404
|
+
const duplicate = !!replaceWith && params.some((param, i) => existing.test(param.substring(codeStarts[i])));
|
|
405
|
+
// comments in front of a removed argument usually describe the previous one, so they are kept
|
|
406
|
+
let carried = '';
|
|
407
|
+
const kept = params.reduce((arr, param, i) => {
|
|
408
|
+
const codeStart = codeStarts[i];
|
|
409
|
+
if (reg.test(param.substring(codeStart))) {
|
|
410
|
+
if (!change.remove && !duplicate) {
|
|
411
|
+
// the name sits at the code start, so renaming must not search the comments
|
|
412
|
+
arr.push(carried + this.commentBreak(carried, param, eol) + param.substring(0, codeStart)
|
|
413
|
+
+ change.replaceWith + param.substring(codeStart + change.name.length));
|
|
414
|
+
carried = '';
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
carried += param.substring(0, codeStart).trimEnd();
|
|
418
|
+
}
|
|
402
419
|
}
|
|
403
|
-
|
|
420
|
+
else {
|
|
421
|
+
arr.push(carried + this.commentBreak(carried, param, eol) + param);
|
|
422
|
+
carried = '';
|
|
423
|
+
}
|
|
424
|
+
return arr;
|
|
425
|
+
}, []);
|
|
426
|
+
fileContent = fileContent.substring(0, call.bodyStart)
|
|
427
|
+
+ kept.join(',') + carried + this.commentBreak(carried, trailing, eol)
|
|
428
|
+
+ trailing
|
|
429
|
+
+ fileContent.substring(call.bodyEnd);
|
|
430
|
+
overwrite = true;
|
|
404
431
|
}
|
|
405
432
|
}
|
|
406
433
|
if (overwrite) {
|
|
407
434
|
this.host.overwrite(entryPath, fileContent);
|
|
408
435
|
}
|
|
409
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Returns the argument list boundaries of every top-level `owner(...)` call in the content.
|
|
439
|
+
* Strings, comments and `url()` tokens are scanned over, so an `owner(` that is only mentioned
|
|
440
|
+
* in one of those is not taken for a call, and anything the stylesheet declares itself is left
|
|
441
|
+
* alone. The brackets are tracked too, so a call nested in another one -
|
|
442
|
+
* `@include scrollbar(scrollbar-theme($sb-size: 6px))` - reports its own closing bracket
|
|
443
|
+
* rather than the one of the call surrounding it.
|
|
444
|
+
*
|
|
445
|
+
* An owner is matched anywhere in a name, because migrations rely on it: `circular-theme` is
|
|
446
|
+
* how `progress-circular-theme` is addressed, and `theme` covers every `*-theme` mixin.
|
|
447
|
+
*/
|
|
448
|
+
findFunctionCalls(content, owner, indented) {
|
|
449
|
+
const calls = [];
|
|
450
|
+
const opening = `${owner}(`;
|
|
451
|
+
const code = this.sassCode(content);
|
|
452
|
+
// Earlier changes can move both calls and scope boundaries, so collect them together.
|
|
453
|
+
const scopes = this.declarationScopes(content, code, indented);
|
|
454
|
+
let brackets = 0;
|
|
455
|
+
for (let i = 0; i < content.length; i++) {
|
|
456
|
+
if (code[i] === '(' || code[i] === '[') {
|
|
457
|
+
brackets++;
|
|
458
|
+
}
|
|
459
|
+
else if (code[i] === ')' || code[i] === ']') {
|
|
460
|
+
brackets--;
|
|
461
|
+
}
|
|
462
|
+
if (!code.startsWith(opening, i) || this.isOwnMember(code, i, owner, scopes, indented && !brackets)) {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const bodyStart = i + opening.length;
|
|
466
|
+
const bodyEnd = this.findClosingBracket(content, bodyStart);
|
|
467
|
+
if (bodyEnd === -1) {
|
|
468
|
+
// unbalanced from here on, leave this call rather than the rest of the file
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
calls.push({ bodyStart, bodyEnd });
|
|
472
|
+
// a same-owner call nested in this one is already covered by it
|
|
473
|
+
i = bodyEnd;
|
|
474
|
+
}
|
|
475
|
+
return calls;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Returns the index of the bracket closing the one `start` is inside of, or -1 when the
|
|
479
|
+
* content is unbalanced. Brackets in strings, comments and `url()` tokens are ignored.
|
|
480
|
+
*/
|
|
481
|
+
findClosingBracket(content, start) {
|
|
482
|
+
let level = 0;
|
|
483
|
+
for (let i = start; i < content.length; i++) {
|
|
484
|
+
const nonCodeEnd = this.skipNonCode(content, i);
|
|
485
|
+
if (nonCodeEnd !== -1) {
|
|
486
|
+
i = nonCodeEnd;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const char = content[i];
|
|
490
|
+
if (char === '(') {
|
|
491
|
+
level++;
|
|
492
|
+
}
|
|
493
|
+
else if (char === ')') {
|
|
494
|
+
if (!level) {
|
|
495
|
+
return i;
|
|
496
|
+
}
|
|
497
|
+
level--;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return -1;
|
|
501
|
+
}
|
|
502
|
+
/** Collects declarations within their brace or indentation scopes in code with non-code masked. */
|
|
503
|
+
declarationScopes(content, code, indented) {
|
|
504
|
+
const scopes = [];
|
|
505
|
+
const stack = [];
|
|
506
|
+
let brackets = 0;
|
|
507
|
+
let pendingDeclaration;
|
|
508
|
+
const enterScope = (start, indentation = 0) => {
|
|
509
|
+
const scope = {
|
|
510
|
+
start, end: code.length, mixins: new Map(), functions: new Map(), declarations: []
|
|
511
|
+
};
|
|
512
|
+
scopes.push(scope);
|
|
513
|
+
// Braces in an interpolated default argument do not start the declaration's body.
|
|
514
|
+
const declaration = brackets ? undefined : pendingDeclaration;
|
|
515
|
+
stack.push({ scope, indentation, declaration });
|
|
516
|
+
if (declaration) {
|
|
517
|
+
pendingDeclaration = undefined;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const leaveScope = (end) => {
|
|
521
|
+
const { scope, declaration } = stack.pop();
|
|
522
|
+
scope.end = end;
|
|
523
|
+
if (declaration) {
|
|
524
|
+
declaration.end = end;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
enterScope(0);
|
|
528
|
+
const declaration = /(?:@(mixin|function)\s+|=\s*)([\w-]+)/y;
|
|
529
|
+
for (let i = 0; i < code.length; i++) {
|
|
530
|
+
// Indentation inside a multiline argument/list is not a new Sass scope.
|
|
531
|
+
if (indented && !brackets && (i === 0 || code[i - 1] === '\n')) {
|
|
532
|
+
const lineEnd = code.indexOf('\n', i);
|
|
533
|
+
const line = code.substring(i, lineEnd === -1 ? code.length : lineEnd);
|
|
534
|
+
if (line.trim()) {
|
|
535
|
+
// Masked comments are whitespace too, but are not part of the source indentation.
|
|
536
|
+
const indentation = /^[ \t]*/.exec(content.substring(i))[0].length;
|
|
537
|
+
// An indented mixin may have defaults but no body.
|
|
538
|
+
if (pendingDeclaration && indentation <= stack[stack.length - 1].indentation) {
|
|
539
|
+
pendingDeclaration.end = i;
|
|
540
|
+
pendingDeclaration = undefined;
|
|
541
|
+
}
|
|
542
|
+
while (stack.length > 1 && indentation < stack[stack.length - 1].indentation) {
|
|
543
|
+
leaveScope(i);
|
|
544
|
+
}
|
|
545
|
+
if (indentation > stack[stack.length - 1].indentation) {
|
|
546
|
+
enterScope(i, indentation);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const char = code[i];
|
|
551
|
+
if (!indented && char === '{') {
|
|
552
|
+
enterScope(i);
|
|
553
|
+
}
|
|
554
|
+
else if (!indented && char === '}' && stack.length > 1) {
|
|
555
|
+
leaveScope(i);
|
|
556
|
+
}
|
|
557
|
+
else if (char === '(' || char === '[') {
|
|
558
|
+
brackets++;
|
|
559
|
+
}
|
|
560
|
+
else if (char === ')' || char === ']') {
|
|
561
|
+
brackets--;
|
|
562
|
+
}
|
|
563
|
+
else if (char === '@' || (indented && !brackets && char === '='
|
|
564
|
+
&& this.keywordBefore(code, i + 1, true) === '@mixin')) {
|
|
565
|
+
declaration.lastIndex = i;
|
|
566
|
+
const match = declaration.exec(code);
|
|
567
|
+
if (match) {
|
|
568
|
+
const scope = stack[stack.length - 1].scope;
|
|
569
|
+
// Sass treats underscores and hyphens as the same identifier character.
|
|
570
|
+
const members = scope[match[1] === 'function' ? 'functions' : 'mixins'];
|
|
571
|
+
const name = match[2].replace(/_/g, '-');
|
|
572
|
+
if (!members.has(name)) {
|
|
573
|
+
members.set(name, i);
|
|
574
|
+
}
|
|
575
|
+
pendingDeclaration = { start: i, end: code.length };
|
|
576
|
+
scope.declarations.push(pendingDeclaration);
|
|
577
|
+
i = declaration.lastIndex - 1;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return scopes;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Tells whether the `owner` at `index` is part of a member the stylesheet declares itself. A
|
|
585
|
+
* user's own `app-grid-summary-theme` ends with a theme function name, so without this its
|
|
586
|
+
* declaration would lose a parameter its body reads.
|
|
587
|
+
*
|
|
588
|
+
* Only this stylesheet is read. Telling which member a call loaded from another one refers to
|
|
589
|
+
* needs the module rules of `@use` and `@forward` - re-exports, `hide` and `as prefix-*` - so a
|
|
590
|
+
* member of the app that another stylesheet declares is still taken for the Ignite UI one.
|
|
591
|
+
*/
|
|
592
|
+
isOwnMember(content, index, owner, scopes, indented) {
|
|
593
|
+
let start = index;
|
|
594
|
+
while (start > 0 && /[\w-]/.test(content[start - 1])) {
|
|
595
|
+
start--;
|
|
596
|
+
}
|
|
597
|
+
// a namespace holds the members of another stylesheet, never the ones declared here
|
|
598
|
+
if (content[start - 1] === '.') {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
const keyword = this.keywordBefore(content, start, indented);
|
|
602
|
+
if (keyword === '@mixin' || keyword === '@function') {
|
|
603
|
+
// a declaration, whose body reads the parameters it lists
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
const name = content.substring(start, index + owner.length).replace(/_/g, '-');
|
|
607
|
+
// Bodies and defaults can run after a later declaration becomes available. Keep their
|
|
608
|
+
// existing protection; declaration order is conclusive only for immediate calls.
|
|
609
|
+
const deferred = scopes.some(scope => scope.declarations.some(member => member.start <= index && index < member.end));
|
|
610
|
+
// a mixin and a function of one name are separate members, so only the called one shadows
|
|
611
|
+
return scopes.some(scope => {
|
|
612
|
+
const declaredAt = (keyword === '@include' ? scope.mixins : scope.functions).get(name);
|
|
613
|
+
return scope.start <= index && index < scope.end && declaredAt !== undefined
|
|
614
|
+
&& (deferred || declaredAt <= index);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/** The at-rule before a member name, or indented shorthand when outside an expression. */
|
|
618
|
+
keywordBefore(content, start, indented) {
|
|
619
|
+
let end = start;
|
|
620
|
+
while (end > 0 && /\s/.test(content[end - 1])) {
|
|
621
|
+
end--;
|
|
622
|
+
}
|
|
623
|
+
// Shorthand starts a statement. Unlike '=', '+' must be adjacent to the mixin name.
|
|
624
|
+
const shorthand = content[end - 1];
|
|
625
|
+
if (indented && (shorthand === '=' || (shorthand === '+' && end === start))
|
|
626
|
+
&& !content.substring(content.lastIndexOf('\n', end - 1) + 1, end - 1).trim()) {
|
|
627
|
+
return shorthand === '=' ? '@mixin' : '@include';
|
|
628
|
+
}
|
|
629
|
+
let keywordStart = end;
|
|
630
|
+
while (keywordStart > 0 && /[\w@-]/.test(content[keywordStart - 1])) {
|
|
631
|
+
keywordStart--;
|
|
632
|
+
}
|
|
633
|
+
return content.substring(keywordStart, end);
|
|
634
|
+
}
|
|
635
|
+
/** Masks non-code with whitespace, preserving offsets, line endings and the original stylesheet. */
|
|
636
|
+
sassCode(content) {
|
|
637
|
+
const parts = [];
|
|
638
|
+
let start = 0;
|
|
639
|
+
for (let i = 0; i < content.length; i++) {
|
|
640
|
+
const nonCodeEnd = this.skipNonCode(content, i);
|
|
641
|
+
if (nonCodeEnd !== -1) {
|
|
642
|
+
parts.push(content.substring(start, i), content.substring(i, nonCodeEnd + 1).replace(/[^\r\n]/g, ' '));
|
|
643
|
+
start = nonCodeEnd + 1;
|
|
644
|
+
i = nonCodeEnd;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
parts.push(content.substring(start));
|
|
648
|
+
return parts.join('');
|
|
649
|
+
}
|
|
650
|
+
/** Index where an argument's code starts, after its leading whitespace and comments. */
|
|
651
|
+
leadingCommentsEnd(param) {
|
|
652
|
+
let i = 0;
|
|
653
|
+
while (i < param.length) {
|
|
654
|
+
if (/\s/.test(param[i])) {
|
|
655
|
+
i++;
|
|
656
|
+
}
|
|
657
|
+
else if (param[i] === '/' && (param[i + 1] === '/' || param[i + 1] === '*')) {
|
|
658
|
+
i = this.skipNonCode(param, i) + 1;
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return i;
|
|
665
|
+
}
|
|
666
|
+
/** The line break a carried `//` comment needs so that it does not swallow what follows it. */
|
|
667
|
+
commentBreak(carried, next, eol) {
|
|
668
|
+
return /\/\/[^\n]*$/.test(carried) && !/^[ \t]*\r?\n/.test(next) ? eol : '';
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* When a string, a comment or a `url()` token starts at `index`, returns the index of its last
|
|
672
|
+
* character so that a scan can carry on past it. Returns -1 when `index` is on code.
|
|
673
|
+
*/
|
|
674
|
+
skipNonCode(content, index) {
|
|
675
|
+
const char = content[index];
|
|
676
|
+
const next = content[index + 1];
|
|
677
|
+
if (char === '\'' || char === '"') {
|
|
678
|
+
return this.skipString(content, index);
|
|
679
|
+
}
|
|
680
|
+
if (char === '/' && next === '*') {
|
|
681
|
+
const end = content.indexOf('*/', index + 2);
|
|
682
|
+
return end === -1 ? content.length : end + 1;
|
|
683
|
+
}
|
|
684
|
+
if (char === '/' && next === '/') {
|
|
685
|
+
const end = content.indexOf('\n', index + 2);
|
|
686
|
+
return end === -1 ? content.length : end;
|
|
687
|
+
}
|
|
688
|
+
if (this.isUrlToken(content, index)) {
|
|
689
|
+
return this.skipUrl(content, index + URL_TOKEN.length);
|
|
690
|
+
}
|
|
691
|
+
return -1;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Sass parses an unquoted `url()` as a single token, so a `//` inside one is part of the
|
|
695
|
+
* address - `url(//cdn.example.com/bg.png)` - and not the start of a comment.
|
|
696
|
+
*/
|
|
697
|
+
isUrlToken(content, index) {
|
|
698
|
+
if (content.substr(index, URL_TOKEN.length).toLowerCase() !== URL_TOKEN) {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
const previous = content[index - 1];
|
|
702
|
+
return previous === undefined || !/[\w-]/.test(previous);
|
|
703
|
+
}
|
|
704
|
+
/** Returns the index of the bracket closing a `url(` whose contents start at `start`. */
|
|
705
|
+
skipUrl(content, start) {
|
|
706
|
+
for (let i = start; i < content.length; i++) {
|
|
707
|
+
const char = content[i];
|
|
708
|
+
if (char === '\'' || char === '"') {
|
|
709
|
+
i = this.skipString(content, i);
|
|
710
|
+
}
|
|
711
|
+
else if (char === ')') {
|
|
712
|
+
return i;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return content.length;
|
|
716
|
+
}
|
|
717
|
+
/** Returns the index of the quote closing the string opened at `start`. */
|
|
718
|
+
skipString(content, start) {
|
|
719
|
+
const quote = content[start];
|
|
720
|
+
for (let i = start + 1; i < content.length; i++) {
|
|
721
|
+
if (content[i] === '\\') {
|
|
722
|
+
i++;
|
|
723
|
+
}
|
|
724
|
+
else if (content[i] === quote) {
|
|
725
|
+
return i;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return content.length;
|
|
729
|
+
}
|
|
410
730
|
isNamedArgument(fileContent, i, occurrences, change) {
|
|
411
731
|
const openingBrackets = [];
|
|
412
732
|
const closingBrackets = [];
|
|
@@ -777,12 +1097,20 @@ class UpdateChanges {
|
|
|
777
1097
|
* prop: inner-func(),
|
|
778
1098
|
* prop2: inner2(inner-param: 3, inner-param: inner-func(..))
|
|
779
1099
|
* ```
|
|
1100
|
+
*
|
|
1101
|
+
* Strings, comments and `url()` tokens are scanned over the same way the rest of the scanner
|
|
1102
|
+
* does it, so a `','` inside one of them is not taken for an argument separator.
|
|
780
1103
|
*/
|
|
781
1104
|
splitFunctionProps(body) {
|
|
782
1105
|
const parts = [];
|
|
783
1106
|
let lastIndex = 0;
|
|
784
1107
|
let level = 0;
|
|
785
1108
|
for (let i = 0; i < body.length; i++) {
|
|
1109
|
+
const nonCodeEnd = this.skipNonCode(body, i);
|
|
1110
|
+
if (nonCodeEnd !== -1) {
|
|
1111
|
+
i = nonCodeEnd;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
786
1114
|
const char = body[i];
|
|
787
1115
|
switch (char) {
|
|
788
1116
|
case '(':
|