versioned-d.ts-tools 0.7.2 → 0.7.3

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.
@@ -10,6 +10,7 @@ export interface WhatsNewConfig {
10
10
  excludedFieldPatterns?: string[];
11
11
  includeStaticFields?: boolean;
12
12
  includeEnums?: boolean;
13
+ filterUnionAdditions?: boolean;
13
14
  linkConfigs?: LinkConfig[];
14
15
  replacements?: any[];
15
16
  }
@@ -21,6 +22,7 @@ export interface MarkdownOptions {
21
22
  excludedFieldPatterns?: string[];
22
23
  includeStaticFields?: boolean;
23
24
  includeEnums?: boolean;
25
+ filterUnionAdditions?: boolean;
24
26
  }
25
27
  export declare const DEFAULT_LINK_CONFIGS: LinkConfig[];
26
28
  declare enum ClassType {
@@ -57,7 +59,23 @@ export declare class APISet {
57
59
  constructor();
58
60
  addClass(clas: ClassStruct): void;
59
61
  containsClass(clas: ClassStruct): boolean;
62
+ /**
63
+ * Checks if two field declaration strings are equivalent except for additional union types.
64
+ * This helps filter out noise from new union options being added to existing parameters.
65
+ */
66
+ private isOnlyUnionAddition;
60
67
  containsField(clas: ClassStruct, field: FieldStruct): boolean;
68
+ containsFieldOrUnionAddition(clas: ClassStruct, field: FieldStruct): boolean;
69
+ /**
70
+ * Checks if a class name appears only in union type contexts and not as standalone types.
71
+ * Returns true if the class only appears in union expressions like "Range | RangeAreas"
72
+ * and doesn't appear as standalone return types or parameters.
73
+ */
74
+ /**
75
+ * Checks if a class with filtered fields should be excluded due to union-only changes.
76
+ * This is called during markdown generation after field exclusions have been applied.
77
+ */
78
+ private shouldExcludeClassForUnionFiltering;
61
79
  diff(other: APISet): APISet;
62
80
  getAsDTS(): string;
63
81
  /**
@@ -165,10 +165,125 @@ class APISet {
165
165
  containsClass(clas) {
166
166
  return this.api.some(element => element.declarationString === clas.declarationString);
167
167
  }
168
+ /**
169
+ * Checks if two field declaration strings are equivalent except for additional union types.
170
+ * This helps filter out noise from new union options being added to existing parameters.
171
+ */
172
+ isOnlyUnionAddition(newDeclaration, oldDeclaration) {
173
+ // If they're identical, no difference at all
174
+ if (newDeclaration === oldDeclaration) {
175
+ return false;
176
+ }
177
+ // More sophisticated approach: extract and compare union types from specific parameters
178
+ // This handles cases where union additions are in the middle of parameter lists
179
+ // Normalize whitespace first
180
+ const oldNormalized = oldDeclaration.replace(/\s+/g, ' ').trim();
181
+ const newNormalized = newDeclaration.replace(/\s+/g, ' ').trim();
182
+ // Find all union type patterns in both declarations
183
+ // Pattern matches: paramName?: Type1 | Type2 | Type3
184
+ const unionPattern = /(\w+\?\s*:\s*)([^;,)]+)/g;
185
+ const oldUnions = new Map();
186
+ const newUnions = new Map();
187
+ let match;
188
+ // Extract union types from old declaration
189
+ unionPattern.lastIndex = 0;
190
+ while ((match = unionPattern.exec(oldNormalized)) !== null) {
191
+ const paramName = match[1].trim();
192
+ const unionString = match[2].trim();
193
+ const unionTypes = unionString.split('|').map(t => t.trim().replace(/^["']|["']$/g, ''));
194
+ oldUnions.set(paramName, unionTypes);
195
+ }
196
+ // Extract union types from new declaration
197
+ unionPattern.lastIndex = 0;
198
+ while ((match = unionPattern.exec(newNormalized)) !== null) {
199
+ const paramName = match[1].trim();
200
+ const unionString = match[2].trim();
201
+ const unionTypes = unionString.split('|').map(t => t.trim().replace(/^["']|["']$/g, ''));
202
+ newUnions.set(paramName, unionTypes);
203
+ }
204
+ // Check if we found any union parameters
205
+ if (oldUnions.size === 0 || newUnions.size === 0) {
206
+ return false;
207
+ }
208
+ // Check if the parameters are the same except for union additions
209
+ let hasUnionAddition = false;
210
+ for (const [paramName, oldTypes] of oldUnions) {
211
+ const newTypes = newUnions.get(paramName);
212
+ if (!newTypes) {
213
+ // Parameter removed, not a union addition
214
+ return false;
215
+ }
216
+ // Check if all old types are present in new types
217
+ const allOldTypesPresent = oldTypes.every(oldType => newTypes.some(newType => newType === oldType));
218
+ if (!allOldTypesPresent) {
219
+ // Types were removed or changed, not just added
220
+ return false;
221
+ }
222
+ // Check if new types were added
223
+ if (newTypes.length > oldTypes.length) {
224
+ hasUnionAddition = true;
225
+ }
226
+ }
227
+ // Check for any completely new parameters (not allowed for union addition)
228
+ for (const paramName of newUnions.keys()) {
229
+ if (!oldUnions.has(paramName)) {
230
+ return false;
231
+ }
232
+ }
233
+ return hasUnionAddition;
234
+ }
168
235
  containsField(clas, field) {
169
236
  const targetClass = this.api.find(element => element.declarationString === clas.declarationString);
170
237
  return targetClass ? targetClass.fields.some(thisField => thisField.declarationString === field.declarationString) : false;
171
238
  }
239
+ containsFieldOrUnionAddition(clas, field) {
240
+ const targetClass = this.api.find(element => element.declarationString === clas.declarationString);
241
+ if (!targetClass) {
242
+ return false;
243
+ }
244
+ return targetClass.fields.some(thisField => {
245
+ // Exact match
246
+ if (thisField.declarationString === field.declarationString) {
247
+ return true;
248
+ }
249
+ // Check if it's only a union addition
250
+ return this.isOnlyUnionAddition(field.declarationString, thisField.declarationString);
251
+ });
252
+ }
253
+ /**
254
+ * Checks if a class name appears only in union type contexts and not as standalone types.
255
+ * Returns true if the class only appears in union expressions like "Range | RangeAreas"
256
+ * and doesn't appear as standalone return types or parameters.
257
+ */
258
+ /**
259
+ * Checks if a class with filtered fields should be excluded due to union-only changes.
260
+ * This is called during markdown generation after field exclusions have been applied.
261
+ */
262
+ shouldExcludeClassForUnionFiltering(clas, originalFields, excludedFieldPatterns = [], excludedFieldNames = []) {
263
+ // If no fields in the class, don't exclude for union reasons
264
+ if (originalFields.length === 0) {
265
+ return false;
266
+ }
267
+ // First filter out excluded fields to see what would actually be displayed
268
+ const visibleFields = originalFields.filter(field => {
269
+ const isExcludedByPattern = excludedFieldPatterns.some(pattern => {
270
+ try {
271
+ return new RegExp(pattern, 'g').test(field.declarationString);
272
+ }
273
+ catch (error) {
274
+ return false;
275
+ }
276
+ });
277
+ const isExcludedByName = excludedFieldNames.includes(field.name);
278
+ return !isExcludedByPattern && !isExcludedByName;
279
+ });
280
+ // If no fields would be visible after exclusions, and all original fields contain unions,
281
+ // then this class likely appears only due to union additions in other classes
282
+ if (visibleFields.length === 0 && originalFields.every(field => field.declarationString.includes('|'))) {
283
+ return true;
284
+ }
285
+ return false;
286
+ }
172
287
  // finds the new fields and classes
173
288
  diff(other) {
174
289
  const diffAPI = new APISet();
@@ -176,16 +291,21 @@ class APISet {
176
291
  if (other.containsClass(element)) {
177
292
  let classShell = null;
178
293
  element.fields.forEach((field) => {
179
- if (!other.containsField(element, field)) {
294
+ const fieldExists = other.containsField(element, field);
295
+ if (!fieldExists) {
180
296
  if (classShell === null) {
181
297
  classShell = element.copyWithoutFields();
182
- diffAPI.addClass(classShell);
183
298
  }
184
299
  classShell.fields.push(field);
185
300
  }
186
301
  });
302
+ // Only add the class to diffAPI if it has fields after filtering
303
+ if (classShell !== null && classShell.fields.length > 0) {
304
+ diffAPI.addClass(classShell);
305
+ }
187
306
  }
188
307
  else {
308
+ // Add new classes to the diff
189
309
  diffAPI.addClass(element);
190
310
  }
191
311
  });
@@ -242,19 +362,15 @@ class APISet {
242
362
  const isEnum = clas.type === ClassType.Enum;
243
363
  if ((finalOptions.includeEnums || !isEnum) && !isExcludedByPattern) {
244
364
  const className = clas.getClassName();
245
- // Special handling for <global> - no link
246
- if (className === "<global>") {
247
- output += "|*global*|";
248
- }
249
- else {
250
- output += "|[" + className + "]("
251
- + buildClassLink(finalOptions.relativePath, className, finalOptions.linkConfigs) + ")|";
365
+ // Apply union filtering if enabled - check if this class should be filtered out BEFORE field filtering
366
+ if (finalOptions.filterUnionAdditions) {
367
+ if (this.shouldExcludeClassForUnionFiltering(clas, clas.fields, finalOptions.excludedFieldPatterns || [], finalOptions.excludedFieldNames || [])) {
368
+ // Skip this class as it only contains union-related changes
369
+ return;
370
+ }
252
371
  }
253
- // Ignore the following:
254
- // - Fields matching excluded patterns (configurable, no defaults - all fields included unless explicitly excluded)
255
- // - Excluded field names (configurable, no defaults - all field names included unless explicitly excluded)
256
- // - Static fields (configurable via includeStaticFields, default: true - static fields included by default)
257
- let filteredFields = clas.fields.filter((field) => {
372
+ // Apply field filtering after union filtering check
373
+ const filteredFields = clas.fields.filter((field) => {
258
374
  // Check if field matches any excluded pattern
259
375
  // Note: Test against field declaration string, just like the original code
260
376
  const isExcludedByPattern = (finalOptions.excludedFieldPatterns || []).some(pattern => {
@@ -272,6 +388,19 @@ class APISet {
272
388
  !(finalOptions.excludedFieldNames || []).includes(field.name) &&
273
389
  (finalOptions.includeStaticFields || !isStaticField));
274
390
  });
391
+ // Special handling for <global> - no link
392
+ if (className === "<global>") {
393
+ output += "|*global*|";
394
+ }
395
+ else {
396
+ output += "|[" + className + "]("
397
+ + buildClassLink(finalOptions.relativePath, className, finalOptions.linkConfigs) + ")|";
398
+ }
399
+ // Ignore the following:
400
+ // - Fields matching excluded patterns (configurable, no defaults - all fields included unless explicitly excluded)
401
+ // - Excluded field names (configurable, no defaults - all field names included unless explicitly excluded)
402
+ // - Static fields (configurable via includeStaticFields, default: true - static fields included by default)
403
+ // (filteredFields already computed above)
275
404
  let first = true;
276
405
  if (filteredFields.length > 0) {
277
406
  filteredFields.forEach((field) => {
@@ -311,7 +440,8 @@ class APISet {
311
440
  });
312
441
  }
313
442
  else {
314
- output += "||\n";
443
+ // When all fields are excluded, still show the class description
444
+ output += "|" + removeAtLink(extractFirstSentenceFromComment(clas.comment)) + "|\n";
315
445
  }
316
446
  }
317
447
  });
@@ -12,6 +12,7 @@ export interface WhatsNewOptions {
12
12
  excludedFieldPatterns?: string[];
13
13
  includeStaticFields?: boolean;
14
14
  includeEnums?: boolean;
15
+ filterUnionAdditions?: boolean;
15
16
  }
16
17
  /**
17
18
  * Generates "what's new" documentation by comparing two TypeScript definition files.
package/dist/whats-new.js CHANGED
@@ -40,7 +40,7 @@ const dts_utilities_1 = require("./dts-utilities");
40
40
  /**
41
41
  * Loads configuration from a JSON file
42
42
  * @param configFilePath Path to the configuration file
43
- * @returns Configuration object with linkConfigs, excludedFieldNames, excludedClassPatterns, excludedFieldPatterns, includeStaticFields, and includeEnums
43
+ * @returns Configuration object with linkConfigs, excludedFieldNames, excludedClassPatterns, excludedFieldPatterns, includeStaticFields, includeEnums, and filterUnionAdditions
44
44
  */
45
45
  function loadWhatsNewConfig(configFilePath) {
46
46
  try {
@@ -67,7 +67,8 @@ function loadWhatsNewConfig(configFilePath) {
67
67
  excludedClassPatterns: config.excludedClassPatterns,
68
68
  excludedFieldPatterns: config.excludedFieldPatterns,
69
69
  includeStaticFields: config.includeStaticFields,
70
- includeEnums: config.includeEnums
70
+ includeEnums: config.includeEnums,
71
+ filterUnionAdditions: config.filterUnionAdditions
71
72
  };
72
73
  }
73
74
  catch (error) {
@@ -87,7 +88,8 @@ function generateWhatsNew(options) {
87
88
  let effectiveExcludedFieldPatterns = options.excludedFieldPatterns;
88
89
  let effectiveIncludeStaticFields = options.includeStaticFields;
89
90
  let effectiveIncludeEnums = options.includeEnums;
90
- if (options.configFilePath && (!options.linkConfigs || !options.excludedFieldNames || !options.excludedClassPatterns || !options.excludedFieldPatterns || options.includeStaticFields === undefined || options.includeEnums === undefined)) {
91
+ let effectiveFilterUnionAdditions = options.filterUnionAdditions;
92
+ if (options.configFilePath && (!options.linkConfigs || !options.excludedFieldNames || !options.excludedClassPatterns || !options.excludedFieldPatterns || options.includeStaticFields === undefined || options.includeEnums === undefined || options.filterUnionAdditions === undefined)) {
91
93
  const config = loadWhatsNewConfig(options.configFilePath);
92
94
  if (!options.linkConfigs && config.linkConfigs) {
93
95
  effectiveLinkConfigs = config.linkConfigs;
@@ -108,6 +110,9 @@ function generateWhatsNew(options) {
108
110
  // Default to true when loading from config (inclusive by default)
109
111
  effectiveIncludeEnums = config.includeEnums !== undefined ? config.includeEnums : true;
110
112
  }
113
+ if (options.filterUnionAdditions === undefined && config.filterUnionAdditions !== undefined) {
114
+ effectiveFilterUnionAdditions = config.filterUnionAdditions;
115
+ }
111
116
  }
112
117
  // read whole files
113
118
  let wholeRelease = fsx.readFileSync(options.oldDtsPath).toString();
@@ -125,7 +130,8 @@ function generateWhatsNew(options) {
125
130
  excludedClassPatterns: effectiveExcludedClassPatterns,
126
131
  excludedFieldPatterns: effectiveExcludedFieldPatterns,
127
132
  includeStaticFields: effectiveIncludeStaticFields,
128
- includeEnums: effectiveIncludeEnums
133
+ includeEnums: effectiveIncludeEnums,
134
+ filterUnionAdditions: effectiveFilterUnionAdditions
129
135
  }));
130
136
  }
131
137
  // CLI entry point
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versioned-d.ts-tools",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Tools for managing versioned TypeScript definition files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {