versioned-d.ts-tools 0.7.1 → 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
  /**
@@ -82,12 +82,6 @@ function compilePatterns(patterns) {
82
82
  }
83
83
  // Default configurations for known Office applications
84
84
  exports.DEFAULT_LINK_CONFIGS = [
85
- {
86
- pathPattern: "office-scripts",
87
- globalFunctionTemplate: "/{basePath}#officescript-officescript-{fieldName}-function(1)",
88
- classTemplate: "/{basePath}/officescript.{className}",
89
- classMemberTemplate: "/{basePath}/officescript.{className}#officescript-officescript-{className}-{fieldName}{suffix}"
90
- },
91
85
  {
92
86
  pathPattern: ".*", // matches any path as fallback
93
87
  globalFunctionTemplate: "/{basePath}#{hostName}-{fileName}-{fieldName}-function(1)",
@@ -171,10 +165,125 @@ class APISet {
171
165
  containsClass(clas) {
172
166
  return this.api.some(element => element.declarationString === clas.declarationString);
173
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
+ }
174
235
  containsField(clas, field) {
175
236
  const targetClass = this.api.find(element => element.declarationString === clas.declarationString);
176
237
  return targetClass ? targetClass.fields.some(thisField => thisField.declarationString === field.declarationString) : false;
177
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
+ }
178
287
  // finds the new fields and classes
179
288
  diff(other) {
180
289
  const diffAPI = new APISet();
@@ -182,16 +291,21 @@ class APISet {
182
291
  if (other.containsClass(element)) {
183
292
  let classShell = null;
184
293
  element.fields.forEach((field) => {
185
- if (!other.containsField(element, field)) {
294
+ const fieldExists = other.containsField(element, field);
295
+ if (!fieldExists) {
186
296
  if (classShell === null) {
187
297
  classShell = element.copyWithoutFields();
188
- diffAPI.addClass(classShell);
189
298
  }
190
299
  classShell.fields.push(field);
191
300
  }
192
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
+ }
193
306
  }
194
307
  else {
308
+ // Add new classes to the diff
195
309
  diffAPI.addClass(element);
196
310
  }
197
311
  });
@@ -248,19 +362,15 @@ class APISet {
248
362
  const isEnum = clas.type === ClassType.Enum;
249
363
  if ((finalOptions.includeEnums || !isEnum) && !isExcludedByPattern) {
250
364
  const className = clas.getClassName();
251
- // Special handling for <global> - no link
252
- if (className === "<global>") {
253
- output += "|*global*|";
254
- }
255
- else {
256
- output += "|[" + className + "]("
257
- + 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
+ }
258
371
  }
259
- // Ignore the following:
260
- // - Fields matching excluded patterns (configurable, no defaults - all fields included unless explicitly excluded)
261
- // - Excluded field names (configurable, no defaults - all field names included unless explicitly excluded)
262
- // - Static fields (configurable via includeStaticFields, default: true - static fields included by default)
263
- let filteredFields = clas.fields.filter((field) => {
372
+ // Apply field filtering after union filtering check
373
+ const filteredFields = clas.fields.filter((field) => {
264
374
  // Check if field matches any excluded pattern
265
375
  // Note: Test against field declaration string, just like the original code
266
376
  const isExcludedByPattern = (finalOptions.excludedFieldPatterns || []).some(pattern => {
@@ -278,6 +388,19 @@ class APISet {
278
388
  !(finalOptions.excludedFieldNames || []).includes(field.name) &&
279
389
  (finalOptions.includeStaticFields || !isStaticField));
280
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)
281
404
  let first = true;
282
405
  if (filteredFields.length > 0) {
283
406
  filteredFields.forEach((field) => {
@@ -317,7 +440,8 @@ class APISet {
317
440
  });
318
441
  }
319
442
  else {
320
- output += "||\n";
443
+ // When all fields are excluded, still show the class description
444
+ output += "|" + removeAtLink(extractFirstSentenceFromComment(clas.comment)) + "|\n";
321
445
  }
322
446
  }
323
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.1",
3
+ "version": "0.7.3",
4
4
  "description": "Tools for managing versioned TypeScript definition files",
5
5
  "main": "dist/index.js",
6
6
  "bin": {