@seniorsistemas/angular-components 19.6.2 → 19.7.1
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/dynamic-form/dynamic-form/components/lookup/lookup.component.d.ts +2 -1
- package/dynamic-form/dynamic-form/components/lookup/types/lookup-models.d.ts +2 -1
- package/dynamic-form/dynamic-form/form-field/configurations/fields/number-field.d.ts +1 -1
- package/dynamic-form/dynamic-form/form-field/fields/bignumber/bignumber-field.component.d.ts +1 -1
- package/dynamic-form/dynamic-form/form-field/fields/currency/currency-field.component.d.ts +1 -1
- package/dynamic-form/dynamic-form/form-field/fields/text/text-field.component.d.ts +1 -2
- package/esm2022/alert/lib/alert/alert.component.mjs +3 -3
- package/esm2022/dynamic-form/dynamic-form/components/lookup/lookup.component.mjs +1 -1
- package/esm2022/dynamic-form/dynamic-form/components/lookup/types/lookup-models.mjs +1 -1
- package/esm2022/dynamic-form/dynamic-form/form-field/configurations/fields/number-field.mjs +1 -1
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/bignumber/bignumber-field.component.mjs +16 -17
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/currency/currency-field.component.mjs +20 -23
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/number/number-field.component.mjs +9 -10
- package/esm2022/dynamic-form/dynamic-form/form-field/fields/text/text-field.component.mjs +9 -15
- package/esm2022/inline-edit/lib/inline-edit/components/fields/inline-edit-number/inline-edit-number.component.mjs +4 -4
- package/esm2022/inline-edit/lib/inline-edit/fields/inline-edit-number-field.mjs +1 -1
- package/esm2022/inline-edit/lib/inline-edit/inline-edit.module.mjs +4 -5
- package/esm2022/input-mask/lib/input-mask/input-mask.directive.mjs +460 -0
- package/esm2022/input-mask/lib/input-mask/input-mask.module.mjs +16 -0
- package/esm2022/input-mask/public-api.mjs +3 -0
- package/esm2022/input-mask/seniorsistemas-angular-components-input-mask.mjs +5 -0
- package/esm2022/lib/locale/fallback.mjs +3 -1
- package/esm2022/table/lib/table/table-column/column-number-format.utils.mjs +24 -0
- package/esm2022/table/lib/table/table-paging/export-number-format.mjs +104 -0
- package/esm2022/table/lib/table/table-paging/models/export-number-format.enum.mjs +6 -0
- package/esm2022/table/lib/table/table-paging/models/index.mjs +2 -2
- package/esm2022/table/lib/table/table-paging/table-paging.component.mjs +58 -99
- package/fesm2022/seniorsistemas-angular-components-alert.mjs +2 -2
- package/fesm2022/seniorsistemas-angular-components-alert.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs +53 -62
- package/fesm2022/seniorsistemas-angular-components-dynamic-form.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-inline-edit.mjs +6 -7
- package/fesm2022/seniorsistemas-angular-components-inline-edit.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components-input-mask.mjs +480 -0
- package/fesm2022/seniorsistemas-angular-components-input-mask.mjs.map +1 -0
- package/fesm2022/seniorsistemas-angular-components-table.mjs +181 -98
- package/fesm2022/seniorsistemas-angular-components-table.mjs.map +1 -1
- package/fesm2022/seniorsistemas-angular-components.mjs +2 -0
- package/fesm2022/seniorsistemas-angular-components.mjs.map +1 -1
- package/inline-edit/lib/inline-edit/fields/inline-edit-number-field.d.ts +2 -2
- package/inline-edit/lib/inline-edit/inline-edit.module.d.ts +2 -2
- package/input-mask/README.md +183 -0
- package/input-mask/index.d.ts +5 -0
- package/input-mask/lib/input-mask/input-mask.directive.d.ts +103 -0
- package/input-mask/lib/input-mask/input-mask.module.d.ts +7 -0
- package/input-mask/public-api.d.ts +2 -0
- package/package.json +18 -12
- package/schematics/migrations/migrate-dynamic-form-v19/index.d.ts +2 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.js +964 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.js.map +1 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.d.ts +1 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.js +888 -0
- package/schematics/migrations/migrate-dynamic-form-v19/index.test.js.map +1 -0
- package/schematics/migrations/migrate-p-datepicker/index.js +4 -8
- package/schematics/migrations/migrate-p-datepicker/index.js.map +1 -1
- package/schematics/migrations/migrate-p-editor/index.d.ts +2 -0
- package/schematics/migrations/migrate-p-editor/index.js +301 -0
- package/schematics/migrations/migrate-p-editor/index.js.map +1 -0
- package/schematics/migrations/migrate-p-editor/index.test.d.ts +1 -0
- package/schematics/migrations/migrate-p-editor/index.test.js +393 -0
- package/schematics/migrations/migrate-p-editor/index.test.js.map +1 -0
- package/schematics/migrations.json +11 -0
- package/schematics/utils/ast-utils.d.ts +7 -0
- package/schematics/utils/ast-utils.js +20 -0
- package/schematics/utils/ast-utils.js.map +1 -1
- package/schematics/utils/html-utils.d.ts +6 -0
- package/schematics/utils/html-utils.js +15 -0
- package/schematics/utils/html-utils.js.map +1 -0
- package/table/README.md +28 -0
- package/table/lib/table/table-column/column-number-format.utils.d.ts +5 -0
- package/table/lib/table/table-paging/export-number-format.d.ts +15 -0
- package/table/lib/table/table-paging/models/export-number-format.enum.d.ts +4 -0
- package/table/lib/table/table-paging/models/index.d.ts +1 -0
- package/table/lib/table/table-paging/table-paging.component.d.ts +10 -27
|
@@ -0,0 +1,964 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrate = migrate;
|
|
4
|
+
const ts = require("typescript");
|
|
5
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
6
|
+
const workspace_utils_1 = require("../../utils/workspace-utils");
|
|
7
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
8
|
+
const REQUIRED_STYLE = 'node_modules/@seniorsistemas/angular-components/styles.css';
|
|
9
|
+
const DYNAMIC_FORM_MODULE = '@seniorsistemas/angular-components/dynamic-form';
|
|
10
|
+
const EDITOR_MODULE = '@seniorsistemas/angular-components/optional-fields/editor';
|
|
11
|
+
const SENIORSISTEMAS_PATTERN = '@seniorsistemas/angular-components';
|
|
12
|
+
/** FieldType enum (v18) → string literal (v19). null = no equivalent / special handling */
|
|
13
|
+
const FIELD_TYPE_MAP = {
|
|
14
|
+
Autocomplete: 'autocomplete',
|
|
15
|
+
Binary: 'binary',
|
|
16
|
+
Blob: 'blob',
|
|
17
|
+
Boolean: 'boolean',
|
|
18
|
+
BooleanSwitch: 'booleanSwitch',
|
|
19
|
+
Button: 'button',
|
|
20
|
+
Checkbox: 'checkbox',
|
|
21
|
+
Chips: 'chips',
|
|
22
|
+
CountryPhonePicker: 'countryPhonePicker',
|
|
23
|
+
Date: 'date',
|
|
24
|
+
DateTime: 'dateTime',
|
|
25
|
+
Double: 'double',
|
|
26
|
+
Editor: 'editor',
|
|
27
|
+
Enum: 'enum',
|
|
28
|
+
Integer: 'integer',
|
|
29
|
+
LocalDateTime: 'localDateTime',
|
|
30
|
+
Lookup: 'lookup',
|
|
31
|
+
Money: 'money',
|
|
32
|
+
Number: 'number',
|
|
33
|
+
Password: 'password',
|
|
34
|
+
ProfilePicture: 'profilePicture',
|
|
35
|
+
Radio: 'radioButton',
|
|
36
|
+
Slider: 'slider',
|
|
37
|
+
StarRating: 'starRating',
|
|
38
|
+
String: 'string',
|
|
39
|
+
Text: 'textArea',
|
|
40
|
+
TextIA: 'contentGenerator',
|
|
41
|
+
Time: 'time',
|
|
42
|
+
CodeEditor: null,
|
|
43
|
+
Custom: null,
|
|
44
|
+
};
|
|
45
|
+
/** DynamicType enum (v18) member → string literal (v19). */
|
|
46
|
+
const DYNAMIC_TYPE_ENUM_MAP = {
|
|
47
|
+
Row: 'row',
|
|
48
|
+
Section: 'section',
|
|
49
|
+
Fieldset: 'fieldset',
|
|
50
|
+
// Field-type members of v18 DynamicType enum
|
|
51
|
+
Autocomplete: 'autocomplete',
|
|
52
|
+
Binary: 'binary',
|
|
53
|
+
Boolean: 'boolean',
|
|
54
|
+
Chips: 'chips',
|
|
55
|
+
Date: 'date',
|
|
56
|
+
DateTime: 'dateTime',
|
|
57
|
+
LocalDateTime: 'localDateTime',
|
|
58
|
+
Double: 'double',
|
|
59
|
+
Enum: 'enum',
|
|
60
|
+
Integer: 'integer',
|
|
61
|
+
Lookup: 'lookup',
|
|
62
|
+
Money: 'money',
|
|
63
|
+
Radio: 'radioButton',
|
|
64
|
+
String: 'string',
|
|
65
|
+
Text: 'textArea',
|
|
66
|
+
Time: 'time',
|
|
67
|
+
Number: 'number',
|
|
68
|
+
Password: 'password',
|
|
69
|
+
Slider: 'slider',
|
|
70
|
+
};
|
|
71
|
+
const GRID_TYPE_MAP = { Row: 'row' };
|
|
72
|
+
const STRUCTURE_TYPE_MAP = { Section: 'section', Fieldset: 'fieldset' };
|
|
73
|
+
/** v18 field/structure class names that become plain objects in v19 */
|
|
74
|
+
const FIELD_CLASS_NAMES = new Set([
|
|
75
|
+
'FormField',
|
|
76
|
+
'TextField', 'SelectField', 'CalendarField', 'CurrencyField', 'NumberField',
|
|
77
|
+
'RadioButtonField', 'AutocompleteField', 'BooleanField', 'BooleanSwitchField',
|
|
78
|
+
'CheckboxField', 'ChipsField', 'LookupField', 'SliderField', 'TextAreaField',
|
|
79
|
+
'EditorField', 'BlobField', 'CountryPhonePickerField', 'ButtonField', 'StarRatingField',
|
|
80
|
+
'CodeEditorField', 'ProfilePictureField', 'DecimalField', 'BignumberField',
|
|
81
|
+
'TextAreaIAField', 'PasswordField',
|
|
82
|
+
'Section', 'Grid', 'Row', 'Fieldset',
|
|
83
|
+
]);
|
|
84
|
+
/** Mapping from v18 class → v19 interface (for isolated object type assertions) */
|
|
85
|
+
const CLASS_TO_INTERFACE = {
|
|
86
|
+
TextField: 'TextFieldConfig',
|
|
87
|
+
SelectField: 'SelectFieldConfig',
|
|
88
|
+
CalendarField: 'CalendarFieldConfig',
|
|
89
|
+
CurrencyField: 'CurrencyFieldConfig',
|
|
90
|
+
DecimalField: 'NumberFieldConfig',
|
|
91
|
+
NumberField: 'NumberFieldConfig',
|
|
92
|
+
BignumberField: 'NumberFieldConfig',
|
|
93
|
+
RadioButtonField: 'RadioButtonFieldConfig',
|
|
94
|
+
AutocompleteField: 'AutocompleteFieldConfig',
|
|
95
|
+
BooleanField: 'BooleanFieldConfig',
|
|
96
|
+
BooleanSwitchField: 'BooleanSwitchFieldConfig',
|
|
97
|
+
CheckboxField: 'CheckboxFieldConfig',
|
|
98
|
+
ChipsField: 'ChipsFieldConfig',
|
|
99
|
+
LookupField: 'LookupFieldConfig',
|
|
100
|
+
SliderField: 'SliderFieldConfig',
|
|
101
|
+
TextAreaField: 'TextAreaFieldConfig',
|
|
102
|
+
TextAreaIAField: 'ContentGeneratorFieldConfig',
|
|
103
|
+
EditorField: 'EditorFieldConfig',
|
|
104
|
+
BlobField: 'BlobFieldConfig',
|
|
105
|
+
CountryPhonePickerField: 'CountryPhonePickerFieldConfig',
|
|
106
|
+
ButtonField: 'ButtonFieldConfig',
|
|
107
|
+
StarRatingField: 'StarRatingFieldConfig',
|
|
108
|
+
ProfilePictureField: 'ProfilePictureFieldConfig',
|
|
109
|
+
PasswordField: 'PasswordFieldConfig',
|
|
110
|
+
FormField: 'DynamicType',
|
|
111
|
+
Section: 'SectionConfig',
|
|
112
|
+
Grid: 'RowConfig',
|
|
113
|
+
Row: 'RowConfig',
|
|
114
|
+
Fieldset: 'FieldsetConfig',
|
|
115
|
+
CodeEditorField: 'DynamicType',
|
|
116
|
+
};
|
|
117
|
+
/** v18 type annotation names → v19 equivalents */
|
|
118
|
+
const TYPE_ANNOTATION_MAP = {
|
|
119
|
+
FormField: 'DynamicType',
|
|
120
|
+
Field: 'DynamicType',
|
|
121
|
+
TextField: 'DynamicType',
|
|
122
|
+
SelectField: 'DynamicType',
|
|
123
|
+
CalendarField: 'DynamicType',
|
|
124
|
+
CurrencyField: 'DynamicType',
|
|
125
|
+
DecimalField: 'DynamicType',
|
|
126
|
+
NumberField: 'DynamicType',
|
|
127
|
+
BignumberField: 'DynamicType',
|
|
128
|
+
RadioButtonField: 'DynamicType',
|
|
129
|
+
AutocompleteField: 'DynamicType',
|
|
130
|
+
BooleanField: 'DynamicType',
|
|
131
|
+
BooleanSwitchField: 'DynamicType',
|
|
132
|
+
CheckboxField: 'DynamicType',
|
|
133
|
+
ChipsField: 'DynamicType',
|
|
134
|
+
LookupField: 'DynamicType',
|
|
135
|
+
SliderField: 'DynamicType',
|
|
136
|
+
TextAreaField: 'DynamicType',
|
|
137
|
+
TextAreaIAField: 'DynamicType',
|
|
138
|
+
EditorField: 'DynamicType',
|
|
139
|
+
BlobField: 'DynamicType',
|
|
140
|
+
CountryPhonePickerField: 'DynamicType',
|
|
141
|
+
ButtonField: 'DynamicType',
|
|
142
|
+
StarRatingField: 'DynamicType',
|
|
143
|
+
ProfilePictureField: 'DynamicType',
|
|
144
|
+
PasswordField: 'DynamicType',
|
|
145
|
+
CodeEditorField: 'DynamicType',
|
|
146
|
+
DynamicConfig: 'DynamicStructure',
|
|
147
|
+
};
|
|
148
|
+
/** All valid DynamicType `type` string literals in v19 */
|
|
149
|
+
const KNOWN_FIELD_TYPES = new Set([
|
|
150
|
+
'autocomplete', 'binary', 'blob', 'boolean', 'booleanSwitch', 'button', 'checkbox',
|
|
151
|
+
'chips', 'contentGenerator', 'countryPhonePicker', 'date', 'dateTime', 'double',
|
|
152
|
+
'editor', 'enum', 'integer', 'localDateTime', 'lookup', 'money', 'number',
|
|
153
|
+
'password', 'profilePicture', 'radioButton', 'slider', 'starRating', 'string',
|
|
154
|
+
'textArea', 'time',
|
|
155
|
+
]);
|
|
156
|
+
/** Names to always remove from @seniorsistemas imports */
|
|
157
|
+
const NAMES_TO_REMOVE = [
|
|
158
|
+
'FormField', 'Field', 'GridType', 'StructureType', 'DynamicConfig',
|
|
159
|
+
'TextField', 'SelectField', 'CalendarField', 'CurrencyField', 'NumberField',
|
|
160
|
+
'RadioButtonField', 'AutocompleteField', 'BooleanField', 'BooleanSwitchField',
|
|
161
|
+
'CheckboxField', 'ChipsField', 'LookupField', 'SliderField', 'TextAreaField',
|
|
162
|
+
'EditorField', 'BlobField', 'CountryPhonePickerField', 'ButtonField', 'StarRatingField',
|
|
163
|
+
'CodeEditorField', 'ProfilePictureField', 'DecimalField', 'BignumberField',
|
|
164
|
+
'TextAreaIAField', 'PasswordField',
|
|
165
|
+
'Section', 'Grid', 'Row', 'Fieldset',
|
|
166
|
+
// Config types used for v18 type casts
|
|
167
|
+
'TextAreaFieldConfig', 'StarRatingFieldConfig', 'AutocompleteFieldConfig',
|
|
168
|
+
'BooleanFieldConfig', 'BooleanSwitchFieldConfig', 'CalendarFieldConfig',
|
|
169
|
+
'CheckboxFieldConfig', 'ChipsFieldConfig', 'CountryPhonePickerFieldConfig',
|
|
170
|
+
'CurrencyFieldConfig', 'LookupFieldConfig', 'NumberFieldConfig', 'PasswordFieldConfig',
|
|
171
|
+
'ProfilePictureFieldConfig', 'RadioButtonFieldConfig', 'SelectFieldConfig',
|
|
172
|
+
'SliderFieldConfig', 'TextFieldConfig', 'ButtonFieldConfig', 'BlobFieldConfig',
|
|
173
|
+
'SectionConfig', 'RowConfig', 'FieldsetConfig',
|
|
174
|
+
// Old structure types
|
|
175
|
+
'Structure', 'DynamicType',
|
|
176
|
+
];
|
|
177
|
+
// ─── Main entry point ─────────────────────────────────────────────────────────
|
|
178
|
+
function migrate() {
|
|
179
|
+
return (tree, context) => {
|
|
180
|
+
let editorDetected = false;
|
|
181
|
+
const fieldsBindings = [];
|
|
182
|
+
(0, workspace_utils_1.ensureAngularJsonStyle)(tree, REQUIRED_STYLE, context);
|
|
183
|
+
tree.visit((filePath) => {
|
|
184
|
+
if (filePath.includes('/node_modules/') || filePath.includes('/dist/'))
|
|
185
|
+
return;
|
|
186
|
+
if (filePath.endsWith('.ts')) {
|
|
187
|
+
const content = tree.readText(filePath);
|
|
188
|
+
if (!quickCheck(content))
|
|
189
|
+
return;
|
|
190
|
+
const result = migrateTypescriptFile(content, filePath, context);
|
|
191
|
+
if (result.editorDetected)
|
|
192
|
+
editorDetected = true;
|
|
193
|
+
if (result.updated !== content)
|
|
194
|
+
tree.overwrite(filePath, result.updated);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
tree.visit((filePath) => {
|
|
198
|
+
if (filePath.includes('/node_modules/') || filePath.includes('/dist/'))
|
|
199
|
+
return;
|
|
200
|
+
if (filePath.endsWith('.html')) {
|
|
201
|
+
const content = tree.readText(filePath);
|
|
202
|
+
if (!content.includes('[fields]') || !content.includes('s-dynamic-form'))
|
|
203
|
+
return;
|
|
204
|
+
const result = migrateHtmlFile(content);
|
|
205
|
+
if (result.bindings.length > 0) {
|
|
206
|
+
fieldsBindings.push(...result.bindings.map((b) => ({ ...b, htmlPath: filePath })));
|
|
207
|
+
tree.overwrite(filePath, result.updated);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// Third pass: annotate field arrays and warn on FormField patterns in all .ts files
|
|
212
|
+
tree.visit((filePath) => {
|
|
213
|
+
if (filePath.includes('/node_modules/') || filePath.includes('/dist/'))
|
|
214
|
+
return;
|
|
215
|
+
if (!filePath.endsWith('.ts'))
|
|
216
|
+
return;
|
|
217
|
+
const content = tree.readText(filePath);
|
|
218
|
+
if (!quickCheckFieldPatterns(content))
|
|
219
|
+
return;
|
|
220
|
+
const result = migrateFieldDefinitions(content, filePath, context);
|
|
221
|
+
if (result !== content)
|
|
222
|
+
tree.overwrite(filePath, result);
|
|
223
|
+
});
|
|
224
|
+
for (const binding of fieldsBindings) {
|
|
225
|
+
addGetterToTsFile(tree, binding, context);
|
|
226
|
+
}
|
|
227
|
+
if (editorDetected) {
|
|
228
|
+
addProviderToApp(tree, 'provideEditorField()', EDITOR_MODULE, context);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
removeFromPackageJson(tree, 'quill', context);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
// ─── Quick-check ──────────────────────────────────────────────────────────────
|
|
236
|
+
const QUICK_CHECK_KEYWORDS = [
|
|
237
|
+
'FormField', 'FieldType', 'DynamicType', 'GridType', 'StructureType',
|
|
238
|
+
'TextField', 'SelectField', 'CalendarField', 'CurrencyField', 'NumberField',
|
|
239
|
+
'RadioButtonField', 'AutocompleteField', 'EditorField', 'DynamicConfig',
|
|
240
|
+
'BooleanField', 'SliderField', 'TextAreaField', 'Field',
|
|
241
|
+
];
|
|
242
|
+
function quickCheck(content) {
|
|
243
|
+
return QUICK_CHECK_KEYWORDS.some((kw) => content.includes(kw));
|
|
244
|
+
}
|
|
245
|
+
function quickCheckFieldPatterns(content) {
|
|
246
|
+
for (const t of KNOWN_FIELD_TYPES) {
|
|
247
|
+
if (content.includes(`type: '${t}'`))
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
// ─── TypeScript migration ──────────────────────────────────────────────────────
|
|
253
|
+
function migrateTypescriptFile(content, filePath, context) {
|
|
254
|
+
const importedNames = getImportedNamesFromSenior(content);
|
|
255
|
+
let updated = content;
|
|
256
|
+
let editorDetected = false;
|
|
257
|
+
// Pass 1: enum accesses → string literals
|
|
258
|
+
const enumResult = migrateEnumAccesses(updated, importedNames, filePath, context);
|
|
259
|
+
updated = enumResult.updated;
|
|
260
|
+
editorDetected = enumResult.editorDetected;
|
|
261
|
+
// Pass 2: new XxxField({...}) → plain object (re-parse after pass 1 changes ranges)
|
|
262
|
+
const constructorsResult = migrateConstructors(updated, importedNames);
|
|
263
|
+
updated = constructorsResult.updated;
|
|
264
|
+
// Pass 2b: { type: 'boolean', representedBy: 'switch' } → { type: 'booleanSwitch' }
|
|
265
|
+
updated = migrateBooleanSwitch(updated);
|
|
266
|
+
// Pass 2c: remove v18 type casts (as TextAreaFieldConfig, etc.)
|
|
267
|
+
updated = removeV18TypeCasts(updated, importedNames);
|
|
268
|
+
// Pass 3: type annotations FormField[] → DynamicType[], DynamicConfig[] → DynamicStructure[]
|
|
269
|
+
updated = migrateTypeAnnotations(updated, importedNames);
|
|
270
|
+
// Pass 3b: @Input() on BaseFieldComponentConfig implementors → input()/input.required()
|
|
271
|
+
if (importedNames.has('BaseFieldComponentConfig')) {
|
|
272
|
+
updated = migrateBaseFieldInputs(updated, filePath, context);
|
|
273
|
+
}
|
|
274
|
+
// Pass 4: cleanup and add imports
|
|
275
|
+
updated = migrateImports(updated, importedNames, editorDetected, filePath, constructorsResult.castTypesUsed);
|
|
276
|
+
// Pass 5: remove FormField from interface heritage clauses, warn for classes
|
|
277
|
+
updated = warnAndFixFormFieldHeritage(updated, filePath, context);
|
|
278
|
+
return { updated, editorDetected };
|
|
279
|
+
}
|
|
280
|
+
function getImportedNamesFromSenior(content) {
|
|
281
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
282
|
+
const names = new Set();
|
|
283
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
284
|
+
if (!ts.isImportDeclaration(node))
|
|
285
|
+
return;
|
|
286
|
+
const spec = node.moduleSpecifier.text;
|
|
287
|
+
if (!spec.includes(SENIORSISTEMAS_PATTERN))
|
|
288
|
+
return;
|
|
289
|
+
const nb = node.importClause?.namedBindings;
|
|
290
|
+
if (!nb || !ts.isNamedImports(nb))
|
|
291
|
+
return;
|
|
292
|
+
for (const el of nb.elements)
|
|
293
|
+
names.add(el.name.text);
|
|
294
|
+
});
|
|
295
|
+
return names;
|
|
296
|
+
}
|
|
297
|
+
// Pass 1 ─────────────────────────────────────────────────────────────────────
|
|
298
|
+
function migrateEnumAccesses(content, importedNames, filePath, context) {
|
|
299
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
300
|
+
const changes = [];
|
|
301
|
+
let editorDetected = false;
|
|
302
|
+
function visit(node) {
|
|
303
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
304
|
+
const enumName = node.expression.getText(sourceFile);
|
|
305
|
+
const memberName = node.name.text;
|
|
306
|
+
if (enumName === 'FieldType' && importedNames.has('FieldType')) {
|
|
307
|
+
if (memberName === 'Editor') {
|
|
308
|
+
editorDetected = true;
|
|
309
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement: "'editor'" });
|
|
310
|
+
}
|
|
311
|
+
else if (memberName === 'Custom') {
|
|
312
|
+
// A6: custom field handling — leave enum access for now, detect in pass 2
|
|
313
|
+
}
|
|
314
|
+
else if (memberName === 'CodeEditor') {
|
|
315
|
+
context.logger.warn(`${filePath}: FieldType.CodeEditor não tem equivalente em v19. Remova ou substitua manualmente.`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const mapped = FIELD_TYPE_MAP[memberName];
|
|
319
|
+
if (mapped !== undefined && mapped !== null) {
|
|
320
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (enumName === 'DynamicType' && importedNames.has('DynamicType')) {
|
|
325
|
+
const mapped = DYNAMIC_TYPE_ENUM_MAP[memberName];
|
|
326
|
+
if (mapped) {
|
|
327
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else if (enumName === 'GridType' && importedNames.has('GridType')) {
|
|
331
|
+
const mapped = GRID_TYPE_MAP[memberName];
|
|
332
|
+
if (mapped) {
|
|
333
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (enumName === 'StructureType' && importedNames.has('StructureType')) {
|
|
337
|
+
const mapped = STRUCTURE_TYPE_MAP[memberName];
|
|
338
|
+
if (mapped) {
|
|
339
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
ts.forEachChild(node, visit);
|
|
344
|
+
}
|
|
345
|
+
visit(sourceFile);
|
|
346
|
+
return {
|
|
347
|
+
updated: changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content,
|
|
348
|
+
editorDetected,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
// Pass 2 ─────────────────────────────────────────────────────────────────────
|
|
352
|
+
function migrateConstructors(content, importedNames) {
|
|
353
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
354
|
+
const changes = [];
|
|
355
|
+
const castTypesUsed = new Set();
|
|
356
|
+
function visit(node) {
|
|
357
|
+
if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
|
|
358
|
+
const calleeName = node.expression.text;
|
|
359
|
+
if (FIELD_CLASS_NAMES.has(calleeName) && importedNames.has(calleeName)) {
|
|
360
|
+
const args = node.arguments;
|
|
361
|
+
if (args && args.length > 0) {
|
|
362
|
+
// v19 has no field classes anymore — `new XxxField(arg)` becomes just `arg`,
|
|
363
|
+
// whether `arg` is an inline object literal or a variable/expression.
|
|
364
|
+
const argText = args[0].getText(sourceFile);
|
|
365
|
+
const inArray = node.parent && ts.isArrayLiteralExpression(node.parent);
|
|
366
|
+
const castType = CLASS_TO_INTERFACE[calleeName] ?? 'DynamicType';
|
|
367
|
+
const body = inArray ? argText : `${argText} as ${castType}`;
|
|
368
|
+
if (!inArray)
|
|
369
|
+
castTypesUsed.add(castType);
|
|
370
|
+
// `() => { ... } as X` misparses the `{` as a block, not an object literal —
|
|
371
|
+
// same issue for a bare `{ ... } as X;` expression statement. Parenthesize.
|
|
372
|
+
const needsParens = (ts.isArrowFunction(node.parent) && node.parent.body === node) ||
|
|
373
|
+
(ts.isExpressionStatement(node.parent) && node.parent.expression === node);
|
|
374
|
+
const replacement = needsParens ? `(${body})` : body;
|
|
375
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
ts.forEachChild(node, visit);
|
|
380
|
+
}
|
|
381
|
+
visit(sourceFile);
|
|
382
|
+
return {
|
|
383
|
+
updated: changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content,
|
|
384
|
+
castTypesUsed,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
// Pass 2b ─────────────────────────────────────────────────────────────────────
|
|
388
|
+
function migrateBooleanSwitch(content) {
|
|
389
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
390
|
+
const changes = [];
|
|
391
|
+
function visit(node) {
|
|
392
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
393
|
+
let typeProp;
|
|
394
|
+
let repProp;
|
|
395
|
+
for (const prop of node.properties) {
|
|
396
|
+
if (!ts.isPropertyAssignment(prop))
|
|
397
|
+
continue;
|
|
398
|
+
const key = prop.name.getText(sourceFile);
|
|
399
|
+
if (key === 'type' && ts.isStringLiteral(prop.initializer) && prop.initializer.text === 'boolean') {
|
|
400
|
+
typeProp = prop;
|
|
401
|
+
}
|
|
402
|
+
if (key === 'representedBy' && ts.isStringLiteral(prop.initializer) && prop.initializer.text === 'switch') {
|
|
403
|
+
repProp = prop;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (typeProp && repProp) {
|
|
407
|
+
// Replace type value
|
|
408
|
+
changes.push({
|
|
409
|
+
start: typeProp.initializer.getStart(),
|
|
410
|
+
end: typeProp.initializer.getEnd(),
|
|
411
|
+
replacement: "'booleanSwitch'",
|
|
412
|
+
});
|
|
413
|
+
// Remove representedBy property with surrounding comma
|
|
414
|
+
const props = [...node.properties];
|
|
415
|
+
const repIdx = props.indexOf(repProp);
|
|
416
|
+
const isLast = repIdx === props.length - 1;
|
|
417
|
+
if (isLast && repIdx > 0) {
|
|
418
|
+
changes.push({
|
|
419
|
+
start: props[repIdx - 1].getEnd(),
|
|
420
|
+
end: repProp.getEnd(),
|
|
421
|
+
replacement: '',
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
else if (!isLast) {
|
|
425
|
+
changes.push({
|
|
426
|
+
start: repProp.getStart(),
|
|
427
|
+
end: props[repIdx + 1].getStart(),
|
|
428
|
+
replacement: '',
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
changes.push({ start: repProp.getStart(), end: repProp.getEnd(), replacement: '' });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
ts.forEachChild(node, visit);
|
|
437
|
+
}
|
|
438
|
+
visit(sourceFile);
|
|
439
|
+
return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
|
|
440
|
+
}
|
|
441
|
+
// Pass 2c ─────────────────────────────────────────────────────────────────────
|
|
442
|
+
function removeV18TypeCasts(content, importedNames) {
|
|
443
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
444
|
+
const changes = [];
|
|
445
|
+
const v18CastNames = new Set([
|
|
446
|
+
...NAMES_TO_REMOVE,
|
|
447
|
+
...Object.values(CLASS_TO_INTERFACE),
|
|
448
|
+
]);
|
|
449
|
+
function visit(node) {
|
|
450
|
+
if (ts.isAsExpression(node) && ts.isTypeReferenceNode(node.type)) {
|
|
451
|
+
const typeName = node.type.typeName.getText(sourceFile);
|
|
452
|
+
if (v18CastNames.has(typeName) && importedNames.has(typeName)) {
|
|
453
|
+
changes.push({
|
|
454
|
+
start: node.getStart(),
|
|
455
|
+
end: node.getEnd(),
|
|
456
|
+
replacement: node.expression.getText(sourceFile),
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
ts.forEachChild(node, visit);
|
|
461
|
+
}
|
|
462
|
+
visit(sourceFile);
|
|
463
|
+
return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
|
|
464
|
+
}
|
|
465
|
+
// Pass 3 ─────────────────────────────────────────────────────────────────────
|
|
466
|
+
function migrateTypeAnnotations(content, importedNames) {
|
|
467
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
468
|
+
const changes = [];
|
|
469
|
+
function visit(node) {
|
|
470
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
471
|
+
const typeName = node.typeName.getText(sourceFile);
|
|
472
|
+
const mapped = TYPE_ANNOTATION_MAP[typeName];
|
|
473
|
+
if (mapped && importedNames.has(typeName)) {
|
|
474
|
+
changes.push({ start: node.getStart(), end: node.getEnd(), replacement: mapped });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
ts.forEachChild(node, visit);
|
|
478
|
+
}
|
|
479
|
+
visit(sourceFile);
|
|
480
|
+
return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
|
|
481
|
+
}
|
|
482
|
+
// Pass 4 — imports ──────────────────────────────────────────────────────────
|
|
483
|
+
function migrateImports(content, originalImportedNames, editorDetected, filePath, castTypesUsed) {
|
|
484
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
485
|
+
// Determine if FieldType is still needed as a type annotation
|
|
486
|
+
const fieldTypeStillUsed = originalImportedNames.has('FieldType') && isUsedAsType(content, 'FieldType');
|
|
487
|
+
// DynamicType (v18 enum) was replaced; if used as a type annotation it maps to the v19 type union
|
|
488
|
+
const dynamicTypeUsedAsAnnotation = isUsedAsType(content, 'DynamicType');
|
|
489
|
+
const toRemove = [...NAMES_TO_REMOVE];
|
|
490
|
+
if (!fieldTypeStillUsed)
|
|
491
|
+
toRemove.push('FieldType');
|
|
492
|
+
const { changes } = (0, ast_utils_1.buildRemoveNamedImportsChanges)(sourceFile, toRemove, SENIORSISTEMAS_PATTERN);
|
|
493
|
+
let updated = (0, ast_utils_1.applyChanges)(content, changes).replace(/\n{3,}/g, '\n\n');
|
|
494
|
+
// Re-parse after removal to check what's currently imported
|
|
495
|
+
const currentlyImported = getImportedNamesFromSenior(updated);
|
|
496
|
+
// Ensure every XxxFieldConfig interface introduced by isolated casts in Pass 2 is imported
|
|
497
|
+
for (const typeName of castTypesUsed) {
|
|
498
|
+
if (!isSymbolImportedFrom(updated, typeName, DYNAMIC_FORM_MODULE)) {
|
|
499
|
+
updated = ensureNamedImport(updated, typeName, DYNAMIC_FORM_MODULE);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Add v19 types as needed
|
|
503
|
+
if (dynamicTypeUsedAsAnnotation &&
|
|
504
|
+
!currentlyImported.has('DynamicType') &&
|
|
505
|
+
!isSymbolImportedFrom(updated, 'DynamicType', DYNAMIC_FORM_MODULE)) {
|
|
506
|
+
updated = ensureNamedImport(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
|
|
507
|
+
}
|
|
508
|
+
if (updated.includes('ExpandedDynamicType') &&
|
|
509
|
+
!isSymbolImportedFrom(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE)) {
|
|
510
|
+
updated = ensureNamedImport(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE);
|
|
511
|
+
}
|
|
512
|
+
if (updated.includes('DynamicStructure') &&
|
|
513
|
+
!isSymbolImportedFrom(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE)) {
|
|
514
|
+
updated = ensureNamedImport(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE);
|
|
515
|
+
}
|
|
516
|
+
if (updated.includes('dynamicTypeToDynamicStructure') &&
|
|
517
|
+
!isSymbolImportedFrom(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE)) {
|
|
518
|
+
updated = ensureNamedImport(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE);
|
|
519
|
+
}
|
|
520
|
+
if (editorDetected && !isSymbolImportedFrom(updated, 'provideEditorField', EDITOR_MODULE)) {
|
|
521
|
+
updated = (0, ast_utils_1.insertImportStatement)(updated, 'provideEditorField', EDITOR_MODULE);
|
|
522
|
+
}
|
|
523
|
+
return updated;
|
|
524
|
+
}
|
|
525
|
+
// ─── HTML migration ────────────────────────────────────────────────────────────
|
|
526
|
+
function migrateHtmlFile(content) {
|
|
527
|
+
const bindings = [];
|
|
528
|
+
let updated = content;
|
|
529
|
+
// Match [fields]="<expr>" inside <s-dynamic-form> tags
|
|
530
|
+
const tagRegex = /(<s-dynamic-form\b[^>]*?)\[fields\]="([^"]+)"([^>]*?>)/gs;
|
|
531
|
+
updated = updated.replace(tagRegex, (match, before, expr, after) => {
|
|
532
|
+
const isDynamic = expr.includes('.');
|
|
533
|
+
const newBindingValue = isDynamic ? `dynamicTypeToConfigs(${expr})` : `${expr}Configs`;
|
|
534
|
+
bindings.push({ propExpression: expr, isDynamic });
|
|
535
|
+
return `${before}[configs]="${newBindingValue}"${after}`;
|
|
536
|
+
});
|
|
537
|
+
return { updated, bindings };
|
|
538
|
+
}
|
|
539
|
+
// ─── Getter injection in TS file ───────────────────────────────────────────────
|
|
540
|
+
function addGetterToTsFile(tree, binding, context) {
|
|
541
|
+
const tsPath = binding.htmlPath.replace(/\.html$/, '.ts');
|
|
542
|
+
if (!tree.exists(tsPath)) {
|
|
543
|
+
context.logger.warn(`${binding.htmlPath}: Não foi possível encontrar ${tsPath} para adicionar getter. Adicione manualmente.`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const content = tree.readText(tsPath);
|
|
547
|
+
if (binding.isDynamic) {
|
|
548
|
+
// Helper method: dynamicTypeToConfigs
|
|
549
|
+
if (content.includes('dynamicTypeToConfigs'))
|
|
550
|
+
return;
|
|
551
|
+
const method = [
|
|
552
|
+
'',
|
|
553
|
+
' protected dynamicTypeToConfigs(fields: DynamicType[]): DynamicStructure[] {',
|
|
554
|
+
' return dynamicTypeToDynamicStructure(fields);',
|
|
555
|
+
' }',
|
|
556
|
+
].join('\n');
|
|
557
|
+
const updated = insertBeforeLastBrace(content, method);
|
|
558
|
+
const withImports = ensureMultipleImports(updated, [
|
|
559
|
+
'DynamicType', 'DynamicStructure', 'dynamicTypeToDynamicStructure',
|
|
560
|
+
], DYNAMIC_FORM_MODULE);
|
|
561
|
+
tree.overwrite(tsPath, withImports);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
// Simple getter: get <prop>Configs()
|
|
565
|
+
const prop = binding.propExpression;
|
|
566
|
+
const getterName = `${prop}Configs`;
|
|
567
|
+
if (content.includes(getterName))
|
|
568
|
+
return;
|
|
569
|
+
const getter = [
|
|
570
|
+
'',
|
|
571
|
+
` protected get ${getterName}(): DynamicStructure[] {`,
|
|
572
|
+
` return dynamicTypeToDynamicStructure(this.${prop});`,
|
|
573
|
+
' }',
|
|
574
|
+
].join('\n');
|
|
575
|
+
const updated = insertBeforeLastBrace(content, getter);
|
|
576
|
+
const withImports = ensureMultipleImports(updated, [
|
|
577
|
+
'DynamicStructure', 'dynamicTypeToDynamicStructure',
|
|
578
|
+
], DYNAMIC_FORM_MODULE);
|
|
579
|
+
tree.overwrite(tsPath, withImports);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function insertBeforeLastBrace(content, insertion) {
|
|
583
|
+
const idx = content.lastIndexOf('}');
|
|
584
|
+
if (idx === -1)
|
|
585
|
+
return content + insertion;
|
|
586
|
+
return content.slice(0, idx) + insertion + '\n' + content.slice(idx);
|
|
587
|
+
}
|
|
588
|
+
// ─── Provider injection ────────────────────────────────────────────────────────
|
|
589
|
+
function addProviderToApp(tree, providerCall, importFrom, context) {
|
|
590
|
+
const candidates = ['src/app/app.config.ts', 'src/app/app.module.ts'];
|
|
591
|
+
for (const candidate of candidates) {
|
|
592
|
+
if (!tree.exists(candidate))
|
|
593
|
+
continue;
|
|
594
|
+
const content = tree.readText(candidate);
|
|
595
|
+
const updated = injectProvider(content, providerCall);
|
|
596
|
+
if (updated !== content) {
|
|
597
|
+
const symbolName = providerCall.replace(/\(.*\)$/, '');
|
|
598
|
+
const withImport = ensureNamedImport(updated, symbolName, importFrom);
|
|
599
|
+
tree.overwrite(candidate, withImport);
|
|
600
|
+
context.logger.info(`${candidate}: adicionado ${providerCall} aos providers.`);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
context.logger.warn(`Não foi possível injetar ${providerCall} automaticamente. Adicione manualmente ao providers do AppModule ou ApplicationConfig.`);
|
|
605
|
+
}
|
|
606
|
+
function injectProvider(content, providerCall) {
|
|
607
|
+
if (content.includes(providerCall))
|
|
608
|
+
return content;
|
|
609
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
610
|
+
const changes = [];
|
|
611
|
+
function visitForProviders(node) {
|
|
612
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
613
|
+
for (const prop of node.properties) {
|
|
614
|
+
if (!ts.isPropertyAssignment(prop))
|
|
615
|
+
continue;
|
|
616
|
+
if (prop.name.getText(sourceFile) !== 'providers')
|
|
617
|
+
continue;
|
|
618
|
+
if (!ts.isArrayLiteralExpression(prop.initializer))
|
|
619
|
+
continue;
|
|
620
|
+
const arr = prop.initializer;
|
|
621
|
+
const els = arr.elements;
|
|
622
|
+
if (els.length > 0) {
|
|
623
|
+
const last = els[els.length - 1];
|
|
624
|
+
changes.push({ start: last.getEnd(), end: last.getEnd(), replacement: `,\n ${providerCall}` });
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
changes.push({ start: arr.getStart() + 1, end: arr.getStart() + 1, replacement: providerCall });
|
|
628
|
+
}
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
ts.forEachChild(node, visitForProviders);
|
|
633
|
+
}
|
|
634
|
+
visitForProviders(sourceFile);
|
|
635
|
+
return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
|
|
636
|
+
}
|
|
637
|
+
// ─── package.json cleanup ─────────────────────────────────────────────────────
|
|
638
|
+
function removeFromPackageJson(tree, dep, context) {
|
|
639
|
+
const pkgPath = '/package.json';
|
|
640
|
+
if (!tree.exists(pkgPath))
|
|
641
|
+
return;
|
|
642
|
+
const pkg = JSON.parse(tree.readText(pkgPath));
|
|
643
|
+
let removed = false;
|
|
644
|
+
for (const section of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
645
|
+
if (pkg[section]?.[dep]) {
|
|
646
|
+
delete pkg[section][dep];
|
|
647
|
+
removed = true;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (removed) {
|
|
651
|
+
tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
652
|
+
context.logger.info(`package.json: removida dependência "${dep}" (sem uso de Editor field).`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
656
|
+
/** Returns true if `symbolName` appears in the file body (not in import declarations) NOT as enum access */
|
|
657
|
+
function isUsedAsType(content, symbolName) {
|
|
658
|
+
// Strip import declarations before checking — the symbol name appears in the import itself
|
|
659
|
+
// and we only care about usage in the code body.
|
|
660
|
+
const withoutImports = content
|
|
661
|
+
.split('\n')
|
|
662
|
+
.filter((line) => !line.trimStart().startsWith('import '))
|
|
663
|
+
.join('\n');
|
|
664
|
+
// After pass 1, all MyEnum.Member accesses are replaced.
|
|
665
|
+
// Any remaining occurrence of symbolName in non-import code is a type annotation.
|
|
666
|
+
const regex = new RegExp(`\\b${symbolName}\\b(?!\\s*\\.)`);
|
|
667
|
+
return regex.test(withoutImports);
|
|
668
|
+
}
|
|
669
|
+
function isSymbolImportedFrom(content, symbolName, fromModule) {
|
|
670
|
+
const escapedModule = fromModule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
671
|
+
const regex = new RegExp(`import\\s*\\{[^}]*\\b${symbolName}\\b[^}]*\\}\\s*from\\s*['"]${escapedModule}['"]`);
|
|
672
|
+
return regex.test(content);
|
|
673
|
+
}
|
|
674
|
+
function ensureNamedImport(content, symbolName, fromModule) {
|
|
675
|
+
if (isSymbolImportedFrom(content, symbolName, fromModule))
|
|
676
|
+
return content;
|
|
677
|
+
// Try to append to an existing import from the same module
|
|
678
|
+
const escapedModule = fromModule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
679
|
+
const existingImportRegex = new RegExp(`(import\\s*\\{)([^}]*)(\\}\\s*from\\s*['"]${escapedModule}['"])`);
|
|
680
|
+
const match = existingImportRegex.exec(content);
|
|
681
|
+
if (match) {
|
|
682
|
+
const newNames = match[2].trimEnd();
|
|
683
|
+
const separator = newNames.endsWith(',') ? ' ' : ', ';
|
|
684
|
+
return content.replace(match[0], `${match[1]}${newNames}${separator}${symbolName}${match[3]}`);
|
|
685
|
+
}
|
|
686
|
+
return (0, ast_utils_1.insertImportStatement)(content, symbolName, fromModule);
|
|
687
|
+
}
|
|
688
|
+
function ensureMultipleImports(content, symbols, fromModule) {
|
|
689
|
+
let result = content;
|
|
690
|
+
for (const sym of symbols) {
|
|
691
|
+
result = ensureNamedImport(result, sym, fromModule);
|
|
692
|
+
}
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
// ─── BaseFieldComponentConfig input migration ─────────────────────────────────
|
|
696
|
+
function migrateBaseFieldInputs(content, filePath, context) {
|
|
697
|
+
if (!content.includes('BaseFieldComponentConfig'))
|
|
698
|
+
return content;
|
|
699
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
700
|
+
const changes = [];
|
|
701
|
+
function visit(node) {
|
|
702
|
+
if (ts.isClassDeclaration(node)) {
|
|
703
|
+
const implementsBaseField = node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword &&
|
|
704
|
+
clause.types.some((t) => t.expression.getText(sourceFile) === 'BaseFieldComponentConfig'));
|
|
705
|
+
if (implementsBaseField) {
|
|
706
|
+
for (const member of node.members) {
|
|
707
|
+
if (!ts.isPropertyDeclaration(member))
|
|
708
|
+
continue;
|
|
709
|
+
if (!hasInputDecorator(member))
|
|
710
|
+
continue;
|
|
711
|
+
if (!ts.isIdentifier(member.name))
|
|
712
|
+
continue;
|
|
713
|
+
if (!member.type)
|
|
714
|
+
continue;
|
|
715
|
+
const propName = member.name.text;
|
|
716
|
+
if (propName !== 'field' && propName !== 'formControl')
|
|
717
|
+
continue;
|
|
718
|
+
const typeStr = unwrapSignalType(member.type, sourceFile).trim();
|
|
719
|
+
const replacement = propName === 'field'
|
|
720
|
+
? `field = input.required<${typeStr}>()`
|
|
721
|
+
: `formControl = input<${typeStr}>()`;
|
|
722
|
+
changes.push({
|
|
723
|
+
start: member.getStart(sourceFile),
|
|
724
|
+
end: member.getEnd(),
|
|
725
|
+
replacement,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
ts.forEachChild(node, visit);
|
|
731
|
+
}
|
|
732
|
+
visit(sourceFile);
|
|
733
|
+
if (changes.length === 0)
|
|
734
|
+
return content;
|
|
735
|
+
let updated = (0, ast_utils_1.applyChanges)(content, changes);
|
|
736
|
+
// Add input() to @angular/core imports
|
|
737
|
+
updated = ensureNamedImport(updated, 'input', '@angular/core');
|
|
738
|
+
// Remove Input from @angular/core if no longer used as decorator
|
|
739
|
+
if (!/@Input\b/.test(updated)) {
|
|
740
|
+
const updatedSf = ts.createSourceFile('_temp.ts', updated, ts.ScriptTarget.Latest, true);
|
|
741
|
+
const { changes: removeChanges } = (0, ast_utils_1.buildRemoveNamedImportsChanges)(updatedSf, ['Input'], '@angular/core');
|
|
742
|
+
if (removeChanges.length > 0) {
|
|
743
|
+
updated = (0, ast_utils_1.applyChanges)(updated, removeChanges);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return updated;
|
|
747
|
+
}
|
|
748
|
+
function hasInputDecorator(node) {
|
|
749
|
+
const decorators = ts.getDecorators(node);
|
|
750
|
+
return (decorators?.some((d) => {
|
|
751
|
+
const expr = ts.isCallExpression(d.expression) ? d.expression.expression : d.expression;
|
|
752
|
+
return ts.isIdentifier(expr) && expr.text === 'Input';
|
|
753
|
+
}) ?? false);
|
|
754
|
+
}
|
|
755
|
+
function unwrapSignalType(typeNode, sourceFile) {
|
|
756
|
+
if (ts.isTypeReferenceNode(typeNode) &&
|
|
757
|
+
typeNode.typeName.getText(sourceFile) === 'Signal' &&
|
|
758
|
+
typeNode.typeArguments?.length === 1) {
|
|
759
|
+
return typeNode.typeArguments[0].getText(sourceFile);
|
|
760
|
+
}
|
|
761
|
+
return typeNode.getText(sourceFile);
|
|
762
|
+
}
|
|
763
|
+
// ─── Field array detection helpers ────────────────────────────────────────────
|
|
764
|
+
/** All elements are plain objects with `name` + known `type` (no spreads). */
|
|
765
|
+
function areAllFieldObjects(elements, sourceFile) {
|
|
766
|
+
if (elements.length === 0)
|
|
767
|
+
return false;
|
|
768
|
+
return elements.every((el) => {
|
|
769
|
+
if (!ts.isObjectLiteralExpression(el))
|
|
770
|
+
return false;
|
|
771
|
+
let hasName = false;
|
|
772
|
+
let hasValidType = false;
|
|
773
|
+
for (const prop of el.properties) {
|
|
774
|
+
if (!ts.isPropertyAssignment(prop))
|
|
775
|
+
continue;
|
|
776
|
+
const key = prop.name.getText(sourceFile);
|
|
777
|
+
if (key === 'name')
|
|
778
|
+
hasName = true;
|
|
779
|
+
if (key === 'type' &&
|
|
780
|
+
ts.isStringLiteral(prop.initializer) &&
|
|
781
|
+
KNOWN_FIELD_TYPES.has(prop.initializer.text)) {
|
|
782
|
+
hasValidType = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return hasName && hasValidType;
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
/** Array has spread elements/assignments AND at least one field-like object — lenient match. */
|
|
789
|
+
function looksLikeFieldArray(elements, sourceFile) {
|
|
790
|
+
if (elements.length === 0)
|
|
791
|
+
return false;
|
|
792
|
+
let hasSpreads = false;
|
|
793
|
+
let hasFieldLikeElement = false;
|
|
794
|
+
for (const el of elements) {
|
|
795
|
+
if (ts.isSpreadElement(el)) {
|
|
796
|
+
hasSpreads = true;
|
|
797
|
+
}
|
|
798
|
+
else if (ts.isObjectLiteralExpression(el)) {
|
|
799
|
+
let hasSpreadAssignment = false;
|
|
800
|
+
let hasName = false;
|
|
801
|
+
let hasValidType = false;
|
|
802
|
+
for (const prop of el.properties) {
|
|
803
|
+
if (ts.isSpreadAssignment(prop))
|
|
804
|
+
hasSpreadAssignment = true;
|
|
805
|
+
if (!ts.isPropertyAssignment(prop))
|
|
806
|
+
continue;
|
|
807
|
+
const key = prop.name.getText(sourceFile);
|
|
808
|
+
if (key === 'name')
|
|
809
|
+
hasName = true;
|
|
810
|
+
if (key === 'type' &&
|
|
811
|
+
ts.isStringLiteral(prop.initializer) &&
|
|
812
|
+
KNOWN_FIELD_TYPES.has(prop.initializer.text)) {
|
|
813
|
+
hasValidType = true;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (hasSpreadAssignment)
|
|
817
|
+
hasSpreads = true;
|
|
818
|
+
if (hasName || hasValidType)
|
|
819
|
+
hasFieldLikeElement = true;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return hasSpreads && hasFieldLikeElement;
|
|
823
|
+
}
|
|
824
|
+
// ─── Field definition migration ───────────────────────────────────────────────
|
|
825
|
+
function migrateFieldDefinitions(content, filePath, context) {
|
|
826
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
827
|
+
const changes = [];
|
|
828
|
+
let needsDynamicTypeImport = false;
|
|
829
|
+
const SPREAD_WARN = `${filePath}: Array com elementos spread detectado — adicionado "as DynamicType[]" para contornar type widening. Verifique se todos os elementos são realmente DynamicType.`;
|
|
830
|
+
function visit(node) {
|
|
831
|
+
// ── Variable declarations without type annotation ─────────────────────
|
|
832
|
+
if (ts.isVariableDeclaration(node) && !node.type && node.initializer && ts.isIdentifier(node.name)) {
|
|
833
|
+
const init = node.initializer;
|
|
834
|
+
if (ts.isArrayLiteralExpression(init)) {
|
|
835
|
+
if (areAllFieldObjects(init.elements, sourceFile)) {
|
|
836
|
+
changes.push({ start: node.name.getEnd(), end: node.name.getEnd(), replacement: ': DynamicType[]' });
|
|
837
|
+
needsDynamicTypeImport = true;
|
|
838
|
+
}
|
|
839
|
+
else if (looksLikeFieldArray(init.elements, sourceFile)) {
|
|
840
|
+
changes.push({ start: node.name.getEnd(), end: node.name.getEnd(), replacement: ': DynamicType[]' });
|
|
841
|
+
context.logger.warn(SPREAD_WARN);
|
|
842
|
+
needsDynamicTypeImport = true;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
else if (ts.isArrowFunction(init) && !init.type && ts.isArrayLiteralExpression(init.body)) {
|
|
846
|
+
const arr = init.body;
|
|
847
|
+
if (areAllFieldObjects(arr.elements, sourceFile)) {
|
|
848
|
+
changes.push({ start: arr.getEnd(), end: arr.getEnd(), replacement: ' as DynamicType[]' });
|
|
849
|
+
needsDynamicTypeImport = true;
|
|
850
|
+
}
|
|
851
|
+
else if (looksLikeFieldArray(arr.elements, sourceFile)) {
|
|
852
|
+
changes.push({ start: arr.getEnd(), end: arr.getEnd(), replacement: ' as DynamicType[]' });
|
|
853
|
+
context.logger.warn(SPREAD_WARN);
|
|
854
|
+
needsDynamicTypeImport = true;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
// ── Function / method declarations without return type ────────────────
|
|
859
|
+
if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
|
|
860
|
+
!node.type &&
|
|
861
|
+
node.body) {
|
|
862
|
+
const hasFieldReturn = node.body.statements.some((stmt) => ts.isReturnStatement(stmt) &&
|
|
863
|
+
stmt.expression !== undefined &&
|
|
864
|
+
ts.isArrayLiteralExpression(stmt.expression) &&
|
|
865
|
+
(areAllFieldObjects(stmt.expression.elements, sourceFile) ||
|
|
866
|
+
looksLikeFieldArray(stmt.expression.elements, sourceFile)));
|
|
867
|
+
if (hasFieldReturn) {
|
|
868
|
+
const pos = node.body.getFullStart();
|
|
869
|
+
changes.push({ start: pos, end: pos, replacement: ': DynamicType[]' });
|
|
870
|
+
needsDynamicTypeImport = true;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
// ── Inline array in function call / constructor arguments ─────────────
|
|
874
|
+
if (ts.isArrayLiteralExpression(node) && !ts.isAsExpression(node.parent)) {
|
|
875
|
+
const parent = node.parent;
|
|
876
|
+
const args = ts.isCallExpression(parent) ? parent.arguments :
|
|
877
|
+
ts.isNewExpression(parent) ? (parent.arguments ?? []) :
|
|
878
|
+
null;
|
|
879
|
+
if (args && [...args].some((arg) => arg === node)) {
|
|
880
|
+
if (areAllFieldObjects(node.elements, sourceFile)) {
|
|
881
|
+
changes.push({ start: node.getEnd(), end: node.getEnd(), replacement: ' as DynamicType[]' });
|
|
882
|
+
needsDynamicTypeImport = true;
|
|
883
|
+
}
|
|
884
|
+
else if (looksLikeFieldArray(node.elements, sourceFile)) {
|
|
885
|
+
changes.push({ start: node.getEnd(), end: node.getEnd(), replacement: ' as DynamicType[]' });
|
|
886
|
+
context.logger.warn(SPREAD_WARN);
|
|
887
|
+
needsDynamicTypeImport = true;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
// ── Object literal with type: 'enum'/'select' but no options ─────────
|
|
892
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
893
|
+
let typeValue;
|
|
894
|
+
let hasOptions = false;
|
|
895
|
+
for (const prop of node.properties) {
|
|
896
|
+
if (!ts.isPropertyAssignment(prop))
|
|
897
|
+
continue;
|
|
898
|
+
const key = prop.name.getText(sourceFile);
|
|
899
|
+
if (key === 'type' && ts.isStringLiteral(prop.initializer)) {
|
|
900
|
+
const val = prop.initializer.text;
|
|
901
|
+
if (val === 'enum' || val === 'select')
|
|
902
|
+
typeValue = val;
|
|
903
|
+
}
|
|
904
|
+
if (key === 'options')
|
|
905
|
+
hasOptions = true;
|
|
906
|
+
}
|
|
907
|
+
if (typeValue && !hasOptions) {
|
|
908
|
+
context.logger.warn(`${filePath}: Campo com type: '${typeValue}' sem a propriedade "options" — SelectFieldConfig requer "options" em v19. Adicione os valores corretos manualmente.`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
ts.forEachChild(node, visit);
|
|
912
|
+
}
|
|
913
|
+
visit(sourceFile);
|
|
914
|
+
let updated = changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
|
|
915
|
+
if (needsDynamicTypeImport) {
|
|
916
|
+
updated = ensureNamedImport(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
|
|
917
|
+
}
|
|
918
|
+
return updated;
|
|
919
|
+
}
|
|
920
|
+
// ─── FormField heritage warning / fix ─────────────────────────────────────────
|
|
921
|
+
function warnAndFixFormFieldHeritage(content, filePath, context) {
|
|
922
|
+
if (!content.includes('FormField'))
|
|
923
|
+
return content;
|
|
924
|
+
const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
925
|
+
const changes = [];
|
|
926
|
+
function visit(node) {
|
|
927
|
+
const isInterface = ts.isInterfaceDeclaration(node);
|
|
928
|
+
const isClass = ts.isClassDeclaration(node);
|
|
929
|
+
if (isInterface || isClass) {
|
|
930
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
931
|
+
const typesArr = [...clause.types];
|
|
932
|
+
for (const typeExpr of typesArr) {
|
|
933
|
+
if (typeExpr.expression.getText(sourceFile) !== 'FormField')
|
|
934
|
+
continue;
|
|
935
|
+
if (isInterface) {
|
|
936
|
+
if (typesArr.length === 1) {
|
|
937
|
+
// Remove the entire extends clause
|
|
938
|
+
changes.push({ start: clause.getFullStart(), end: clause.getEnd(), replacement: '' });
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
const idx = typesArr.indexOf(typeExpr);
|
|
942
|
+
if (idx === 0) {
|
|
943
|
+
changes.push({ start: typeExpr.getStart(), end: typesArr[1].getStart(), replacement: '' });
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
changes.push({ start: typesArr[idx - 1].getEnd(), end: typeExpr.getEnd(), replacement: '' });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
context.logger.warn(`${filePath}: Classe estende "FormField" (removido em v19). Remova "extends FormField", o uso de ConstructorParameters<typeof FormField> e as chamadas super() relacionados manualmente.`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
ts.forEachChild(node, visit);
|
|
957
|
+
}
|
|
958
|
+
visit(sourceFile);
|
|
959
|
+
if (content.includes('ConstructorParameters') && content.includes('FormField')) {
|
|
960
|
+
context.logger.warn(`${filePath}: Uso de ConstructorParameters<typeof FormField> detectado — FormField foi removido em v19. Revise e substitua manualmente.`);
|
|
961
|
+
}
|
|
962
|
+
return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
|
|
963
|
+
}
|
|
964
|
+
//# sourceMappingURL=index.js.map
|