html-validate 7.1.2 → 7.2.0

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/dist/cjs/core.js CHANGED
@@ -3117,7 +3117,7 @@ var TRANSFORMER_API;
3117
3117
  /** @public */
3118
3118
  const name = "html-validate";
3119
3119
  /** @public */
3120
- const version = "7.1.2";
3120
+ const version = "7.2.0";
3121
3121
  /** @public */
3122
3122
  const homepage = "https://html-validate.org";
3123
3123
  /** @public */
@@ -3218,6 +3218,18 @@ function getSchemaValidator(ruleId, properties) {
3218
3218
  };
3219
3219
  return ajv$1.compile(schema);
3220
3220
  }
3221
+ function isErrorDescriptor(value) {
3222
+ return Boolean(value[0] && value[0].message);
3223
+ }
3224
+ function unpackErrorDescriptor(value) {
3225
+ if (isErrorDescriptor(value)) {
3226
+ return value[0];
3227
+ }
3228
+ else {
3229
+ const [node, message, location, context] = value;
3230
+ return { node, message, location, context };
3231
+ }
3232
+ }
3221
3233
  /**
3222
3234
  * @public
3223
3235
  */
@@ -3318,13 +3330,8 @@ class Rule {
3318
3330
  static schema() {
3319
3331
  return null;
3320
3332
  }
3321
- /**
3322
- * Report a new error.
3323
- *
3324
- * Rule must be enabled both globally and on the specific node for this to
3325
- * have any effect.
3326
- */
3327
- report(node, message, location, context) {
3333
+ report(...args) {
3334
+ const { node, message, location, context } = unpackErrorDescriptor(args);
3328
3335
  if (this.isEnabled() && (!node || node.ruleEnabled(this.name))) {
3329
3336
  const where = this.findLocation({ node, location, event: this.event });
3330
3337
  const interpolated = interpolate(message, context !== null && context !== void 0 ? context : {});
@@ -3837,9 +3844,14 @@ class AttrCase extends Rule {
3837
3844
  return;
3838
3845
  }
3839
3846
  const letters = event.key.replace(/[^a-z]+/gi, "");
3840
- if (!this.style.match(letters)) {
3841
- this.report(event.target, `Attribute "${event.key}" should be ${this.style.name}`, event.keyLocation);
3847
+ if (this.style.match(letters)) {
3848
+ return;
3842
3849
  }
3850
+ this.report({
3851
+ node: event.target,
3852
+ message: `Attribute "${event.key}" should be ${this.style.name}`,
3853
+ location: event.keyLocation,
3854
+ });
3843
3855
  });
3844
3856
  }
3845
3857
  isIgnored(node) {
@@ -5139,6 +5151,11 @@ class ElementName extends Rule {
5139
5151
  }
5140
5152
  }
5141
5153
 
5154
+ var ErrorKind;
5155
+ (function (ErrorKind) {
5156
+ ErrorKind["CONTENT"] = "content";
5157
+ ErrorKind["DESCENDANT"] = "descendant";
5158
+ })(ErrorKind || (ErrorKind = {}));
5142
5159
  function getTransparentChildren(node, transparent) {
5143
5160
  if (typeof transparent === "boolean") {
5144
5161
  return node.childElements;
@@ -5152,10 +5169,28 @@ function getTransparentChildren(node, transparent) {
5152
5169
  });
5153
5170
  }
5154
5171
  }
5172
+ function getRuleDescription$1(context) {
5173
+ if (!context) {
5174
+ return [
5175
+ "Some elements has restrictions on what content is allowed.",
5176
+ "This can include both direct children or descendant elements.",
5177
+ ];
5178
+ }
5179
+ switch (context.kind) {
5180
+ case ErrorKind.CONTENT:
5181
+ return [
5182
+ `The \`${context.child}\` element is not permitted as content under the parent \`${context.parent}\` element.`,
5183
+ ];
5184
+ case ErrorKind.DESCENDANT:
5185
+ return [
5186
+ `The \`${context.child}\` element is not permitted as a descendant of the \`${context.ancestor}\` element.`,
5187
+ ];
5188
+ }
5189
+ }
5155
5190
  class ElementPermittedContent extends Rule {
5156
- documentation() {
5191
+ documentation(context) {
5157
5192
  return {
5158
- description: "Some elements has restrictions on what content is allowed. This can include both direct children or descendant elements.",
5193
+ description: getRuleDescription$1(context).join("\n"),
5159
5194
  url: ruleDocumentationUrl("@/rules/element-permitted-content.ts"),
5160
5195
  };
5161
5196
  }
@@ -5164,8 +5199,9 @@ class ElementPermittedContent extends Rule {
5164
5199
  const doc = event.document;
5165
5200
  doc.visitDepthFirst((node) => {
5166
5201
  const parent = node.parent;
5167
- /* dont verify root element, assume any element is allowed */
5168
- if (!parent || parent.isRootElement()) {
5202
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5203
+ * will not yield nodes without a parent */
5204
+ if (!parent) {
5169
5205
  return;
5170
5206
  }
5171
5207
  /* Run each validation step, stop as soon as any errors are
@@ -5175,7 +5211,6 @@ class ElementPermittedContent extends Rule {
5175
5211
  [
5176
5212
  () => this.validatePermittedContent(node, parent),
5177
5213
  () => this.validatePermittedDescendant(node, parent),
5178
- () => this.validatePermittedAncestors(node),
5179
5214
  ].some((fn) => fn());
5180
5215
  });
5181
5216
  });
@@ -5192,7 +5227,14 @@ class ElementPermittedContent extends Rule {
5192
5227
  }
5193
5228
  validatePermittedContentImpl(cur, parent, rules) {
5194
5229
  if (!Validator.validatePermitted(cur, rules)) {
5195
- this.report(cur, `Element <${cur.tagName}> is not permitted as content in ${parent.annotatedName}`);
5230
+ const child = `<${cur.tagName}>`;
5231
+ const message = `${child} element is not permitted as content under ${parent.annotatedName}`;
5232
+ const context = {
5233
+ kind: ErrorKind.CONTENT,
5234
+ parent: parent.annotatedName,
5235
+ child,
5236
+ };
5237
+ this.report(cur, message, null, context);
5196
5238
  return true;
5197
5239
  }
5198
5240
  /* for transparent elements all/listed children must be validated against
@@ -5223,21 +5265,15 @@ class ElementPermittedContent extends Rule {
5223
5265
  if (Validator.validatePermitted(node, rules)) {
5224
5266
  continue;
5225
5267
  }
5226
- this.report(node, `Element <${node.tagName}> is not permitted as descendant of ${cur.annotatedName}`);
5227
- return true;
5228
- }
5229
- return false;
5230
- }
5231
- validatePermittedAncestors(node) {
5232
- if (!node.meta) {
5233
- return false;
5234
- }
5235
- const rules = node.meta.requiredAncestors;
5236
- if (!rules) {
5237
- return false;
5238
- }
5239
- if (!Validator.validateAncestors(node, rules)) {
5240
- this.report(node, `Element <${node.tagName}> requires an "${rules[0]}" ancestor`);
5268
+ const child = `<${node.tagName}>`;
5269
+ const ancestor = cur.annotatedName;
5270
+ const message = `${child} element is not permitted as a descendant of ${ancestor}`;
5271
+ const context = {
5272
+ kind: ErrorKind.DESCENDANT,
5273
+ ancestor,
5274
+ child,
5275
+ };
5276
+ this.report(node, message, null, context);
5241
5277
  return true;
5242
5278
  }
5243
5279
  return false;
@@ -5304,6 +5340,152 @@ class ElementPermittedOrder extends Rule {
5304
5340
  }
5305
5341
  }
5306
5342
 
5343
+ const CACHE_KEY = Symbol(classifyNodeText.name);
5344
+ var TextClassification;
5345
+ (function (TextClassification) {
5346
+ TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5347
+ TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5348
+ TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5349
+ })(TextClassification || (TextClassification = {}));
5350
+ /**
5351
+ * Checks text content of an element.
5352
+ *
5353
+ * Any text is considered including text from descendant elements. Whitespace is
5354
+ * ignored.
5355
+ *
5356
+ * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5357
+ */
5358
+ function classifyNodeText(node) {
5359
+ if (node.cacheExists(CACHE_KEY)) {
5360
+ return node.cacheGet(CACHE_KEY);
5361
+ }
5362
+ const text = findTextNodes(node);
5363
+ /* if any text is dynamic classify as dynamic */
5364
+ if (text.some((cur) => cur.isDynamic)) {
5365
+ return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5366
+ }
5367
+ /* if any text has non-whitespace character classify as static */
5368
+ if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5369
+ return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5370
+ }
5371
+ /* default to empty */
5372
+ return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5373
+ }
5374
+ function findTextNodes(node) {
5375
+ let text = [];
5376
+ for (const child of node.childNodes) {
5377
+ switch (child.nodeType) {
5378
+ case NodeType.TEXT_NODE:
5379
+ text.push(child);
5380
+ break;
5381
+ case NodeType.ELEMENT_NODE:
5382
+ text = text.concat(findTextNodes(child));
5383
+ break;
5384
+ }
5385
+ }
5386
+ return text;
5387
+ }
5388
+
5389
+ function hasAltText(image) {
5390
+ const alt = image.getAttribute("alt");
5391
+ /* missing or boolean */
5392
+ if (alt === null || alt.value === null) {
5393
+ return false;
5394
+ }
5395
+ return alt.isDynamic || alt.value.toString() !== "";
5396
+ }
5397
+
5398
+ function hasAriaLabel(node) {
5399
+ const label = node.getAttribute("aria-label");
5400
+ /* missing or boolean */
5401
+ if (label === null || label.value === null) {
5402
+ return false;
5403
+ }
5404
+ return label.isDynamic || label.value.toString() !== "";
5405
+ }
5406
+
5407
+ /**
5408
+ * Joins a list of words into natural language.
5409
+ *
5410
+ * - `["foo"]` becomes `"foo"`
5411
+ * - `["foo", "bar"]` becomes `"foo or bar"`
5412
+ * - `["foo", "bar", "baz"]` becomes `"foo, bar or baz"`
5413
+ * - and so on...
5414
+ *
5415
+ * @internal
5416
+ * @param values - List of words to join
5417
+ * @param conjunction - Conjunction for the last element.
5418
+ * @returns String with the words naturally joined with a conjunction.
5419
+ */
5420
+ function naturalJoin(values, conjunction = "or") {
5421
+ switch (values.length) {
5422
+ case 0:
5423
+ return "";
5424
+ case 1:
5425
+ return values[0];
5426
+ case 2:
5427
+ return `${values[0]} ${conjunction} ${values[1]}`;
5428
+ default:
5429
+ return `${values.slice(0, -1).join(", ")} ${conjunction} ${values.slice(-1)[0]}`;
5430
+ }
5431
+ }
5432
+
5433
+ function isTagnameOnly(value) {
5434
+ return Boolean(value.match(/^[a-zA-Z0-9-]+$/));
5435
+ }
5436
+ function getRuleDescription(context) {
5437
+ if (!context) {
5438
+ return [
5439
+ "Some elements has restrictions on what content is allowed.",
5440
+ "This can include both direct children or descendant elements.",
5441
+ ];
5442
+ }
5443
+ const escaped = context.ancestor.map((it) => `\`${it}\``);
5444
+ return [`The \`${context.child}\` element requires a ${naturalJoin(escaped)} ancestor.`];
5445
+ }
5446
+ class ElementRequiredAncestor extends Rule {
5447
+ documentation(context) {
5448
+ return {
5449
+ description: getRuleDescription(context).join("\n"),
5450
+ url: ruleDocumentationUrl("@/rules/element-required-ancestor.ts"),
5451
+ };
5452
+ }
5453
+ setup() {
5454
+ this.on("dom:ready", (event) => {
5455
+ const doc = event.document;
5456
+ doc.visitDepthFirst((node) => {
5457
+ const parent = node.parent;
5458
+ /* istanbul ignore next: satisfy typescript but will visitDepthFirst()
5459
+ * will not yield nodes without a parent */
5460
+ if (!parent) {
5461
+ return;
5462
+ }
5463
+ this.validateRequiredAncestors(node);
5464
+ });
5465
+ });
5466
+ }
5467
+ validateRequiredAncestors(node) {
5468
+ if (!node.meta) {
5469
+ return;
5470
+ }
5471
+ const rules = node.meta.requiredAncestors;
5472
+ if (!rules) {
5473
+ return;
5474
+ }
5475
+ if (Validator.validateAncestors(node, rules)) {
5476
+ return;
5477
+ }
5478
+ const ancestor = rules.map((it) => (isTagnameOnly(it) ? `<${it}>` : `"${it}"`));
5479
+ const child = `<${node.tagName}>`;
5480
+ const message = `<${node.tagName}> element requires a ${naturalJoin(ancestor)} ancestor`;
5481
+ const context = {
5482
+ ancestor,
5483
+ child,
5484
+ };
5485
+ this.report(node, message, null, context);
5486
+ }
5487
+ }
5488
+
5307
5489
  class ElementRequiredAttributes extends Rule {
5308
5490
  documentation(context) {
5309
5491
  const docs = {
@@ -5381,52 +5563,6 @@ class ElementRequiredContent extends Rule {
5381
5563
  }
5382
5564
  }
5383
5565
 
5384
- const CACHE_KEY = Symbol(classifyNodeText.name);
5385
- var TextClassification;
5386
- (function (TextClassification) {
5387
- TextClassification[TextClassification["EMPTY_TEXT"] = 0] = "EMPTY_TEXT";
5388
- TextClassification[TextClassification["DYNAMIC_TEXT"] = 1] = "DYNAMIC_TEXT";
5389
- TextClassification[TextClassification["STATIC_TEXT"] = 2] = "STATIC_TEXT";
5390
- })(TextClassification || (TextClassification = {}));
5391
- /**
5392
- * Checks text content of an element.
5393
- *
5394
- * Any text is considered including text from descendant elements. Whitespace is
5395
- * ignored.
5396
- *
5397
- * If any text is dynamic `TextClassification.DYNAMIC_TEXT` is returned.
5398
- */
5399
- function classifyNodeText(node) {
5400
- if (node.cacheExists(CACHE_KEY)) {
5401
- return node.cacheGet(CACHE_KEY);
5402
- }
5403
- const text = findTextNodes(node);
5404
- /* if any text is dynamic classify as dynamic */
5405
- if (text.some((cur) => cur.isDynamic)) {
5406
- return node.cacheSet(CACHE_KEY, TextClassification.DYNAMIC_TEXT);
5407
- }
5408
- /* if any text has non-whitespace character classify as static */
5409
- if (text.some((cur) => cur.textContent.match(/\S/) !== null)) {
5410
- return node.cacheSet(CACHE_KEY, TextClassification.STATIC_TEXT);
5411
- }
5412
- /* default to empty */
5413
- return node.cacheSet(CACHE_KEY, TextClassification.EMPTY_TEXT);
5414
- }
5415
- function findTextNodes(node) {
5416
- let text = [];
5417
- for (const child of node.childNodes) {
5418
- switch (child.nodeType) {
5419
- case NodeType.TEXT_NODE:
5420
- text.push(child);
5421
- break;
5422
- case NodeType.ELEMENT_NODE:
5423
- text = text.concat(findTextNodes(child));
5424
- break;
5425
- }
5426
- }
5427
- return text;
5428
- }
5429
-
5430
5566
  const selector = ["h1", "h2", "h3", "h4", "h5", "h6"].join(",");
5431
5567
  class EmptyHeading extends Rule {
5432
5568
  documentation() {
@@ -7607,24 +7743,6 @@ class TelNonBreaking extends Rule {
7607
7743
  }
7608
7744
  }
7609
7745
 
7610
- function hasAltText(image) {
7611
- const alt = image.getAttribute("alt");
7612
- /* missing or boolean */
7613
- if (alt === null || alt.value === null) {
7614
- return false;
7615
- }
7616
- return alt.isDynamic || alt.value.toString() !== "";
7617
- }
7618
-
7619
- function hasAriaLabel(node) {
7620
- const label = node.getAttribute("aria-label");
7621
- /* missing or boolean */
7622
- if (label === null || label.value === null) {
7623
- return false;
7624
- }
7625
- return label.isDynamic || label.value.toString() !== "";
7626
- }
7627
-
7628
7746
  /**
7629
7747
  * Check if attribute is present and non-empty or dynamic.
7630
7748
  */
@@ -9802,13 +9920,13 @@ class VoidStyle extends Rule {
9802
9920
  return;
9803
9921
  }
9804
9922
  if (this.shouldBeOmitted(node)) {
9805
- this.report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9923
+ this.reportError(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
9806
9924
  }
9807
9925
  if (this.shouldBeSelfClosed(node)) {
9808
- this.report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9926
+ this.reportError(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
9809
9927
  }
9810
9928
  }
9811
- report(node, message) {
9929
+ reportError(node, message) {
9812
9930
  const context = {
9813
9931
  style: this.style,
9814
9932
  tagName: node.tagName,
@@ -10120,6 +10238,7 @@ const bundledRules = {
10120
10238
  "element-permitted-content": ElementPermittedContent,
10121
10239
  "element-permitted-occurrences": ElementPermittedOccurrences,
10122
10240
  "element-permitted-order": ElementPermittedOrder,
10241
+ "element-required-ancestor": ElementRequiredAncestor,
10123
10242
  "element-required-attributes": ElementRequiredAttributes,
10124
10243
  "element-required-content": ElementRequiredContent,
10125
10244
  "empty-heading": EmptyHeading,
@@ -10227,6 +10346,7 @@ const config$1 = {
10227
10346
  "element-permitted-content": "error",
10228
10347
  "element-permitted-occurrences": "error",
10229
10348
  "element-permitted-order": "error",
10349
+ "element-required-ancestor": "error",
10230
10350
  "element-required-attributes": "error",
10231
10351
  "element-required-content": "error",
10232
10352
  "empty-heading": "error",
@@ -10285,6 +10405,7 @@ const config = {
10285
10405
  "element-permitted-content": "error",
10286
10406
  "element-permitted-occurrences": "error",
10287
10407
  "element-permitted-order": "error",
10408
+ "element-required-ancestor": "error",
10288
10409
  "element-required-attributes": "error",
10289
10410
  "element-required-content": "error",
10290
10411
  "multiple-labeled-controls": "error",
@@ -10364,6 +10485,7 @@ class ResolvedConfig {
10364
10485
  });
10365
10486
  }
10366
10487
  catch (err) {
10488
+ /* istanbul ignore next: only used as a fallback */
10367
10489
  const message = err instanceof Error ? err.message : String(err);
10368
10490
  throw new NestedError(`When transforming "${source.filename}": ${message}`, ensureError(err));
10369
10491
  }
@@ -10376,7 +10498,7 @@ class ResolvedConfig {
10376
10498
  * Wrapper around [[transformSource]] which reads a file before passing it
10377
10499
  * as-is to transformSource.
10378
10500
  *
10379
- * @param source - Filename to transform (according to configured
10501
+ * @param filename - Filename to transform (according to configured
10380
10502
  * transformations)
10381
10503
  * @returns A list of transformed sources ready for validation.
10382
10504
  */
@@ -10532,7 +10654,9 @@ class Config {
10532
10654
  var _a;
10533
10655
  const valid = validator(configData);
10534
10656
  if (!valid) {
10535
- throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema, (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10657
+ throw new SchemaValidationError(filename, `Invalid configuration`, configData, configurationSchema,
10658
+ /* istanbul ignore next: will be set when a validation error has occurred */
10659
+ (_a = validator.errors) !== null && _a !== void 0 ? _a : []);
10536
10660
  }
10537
10661
  if (configData.rules) {
10538
10662
  const normalizedRules = Config.getRulesObject(configData.rules);
@@ -10641,6 +10765,7 @@ class Config {
10641
10765
  metaTable.loadFromObject(legacyRequire(entry));
10642
10766
  }
10643
10767
  catch (err) {
10768
+ /* istanbul ignore next: only used as a fallback */
10644
10769
  const message = err instanceof Error ? err.message : String(err);
10645
10770
  throw new ConfigError(`Failed to load elements from "${entry}": ${message}`, ensureError(err));
10646
10771
  }
@@ -10662,6 +10787,7 @@ class Config {
10662
10787
  *
10663
10788
  * @internal primary purpose is unittests
10664
10789
  */
10790
+ /* istanbul ignore next: used for testing only */
10665
10791
  get() {
10666
10792
  const config = { ...this.config };
10667
10793
  if (config.elements) {
@@ -10684,6 +10810,7 @@ class Config {
10684
10810
  */
10685
10811
  getRules() {
10686
10812
  var _a;
10813
+ /* istanbul ignore next: only used as a fallback */
10687
10814
  return Config.getRulesObject((_a = this.config.rules) !== null && _a !== void 0 ? _a : {});
10688
10815
  }
10689
10816
  static getRulesObject(src) {
@@ -10718,6 +10845,7 @@ class Config {
10718
10845
  return plugin;
10719
10846
  }
10720
10847
  catch (err) {
10848
+ /* istanbul ignore next: only used as a fallback */
10721
10849
  const message = err instanceof Error ? err.message : String(err);
10722
10850
  throw new ConfigError(`Failed to load plugin "${moduleName}": ${message}`, ensureError(err));
10723
10851
  }
@@ -11633,9 +11761,9 @@ class Reporter {
11633
11761
  if (!(location.filename in this.result)) {
11634
11762
  this.result[location.filename] = [];
11635
11763
  }
11636
- this.result[location.filename].push({
11764
+ const ruleUrl = (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url;
11765
+ const entry = {
11637
11766
  ruleId: rule.name,
11638
- ruleUrl: (_a = rule.documentation(context)) === null || _a === void 0 ? void 0 : _a.url,
11639
11767
  severity,
11640
11768
  message,
11641
11769
  offset: location.offset,
@@ -11645,8 +11773,14 @@ class Reporter {
11645
11773
  selector() {
11646
11774
  return node ? node.generateSelector() : null;
11647
11775
  },
11648
- context,
11649
- });
11776
+ };
11777
+ if (ruleUrl) {
11778
+ entry.ruleUrl = ruleUrl;
11779
+ }
11780
+ if (context) {
11781
+ entry.context = context;
11782
+ }
11783
+ this.result[location.filename].push(entry);
11650
11784
  }
11651
11785
  addManual(filename, message) {
11652
11786
  if (!(filename in this.result)) {
@@ -12060,7 +12194,7 @@ class Engine {
12060
12194
  offset: location.offset,
12061
12195
  line: location.line,
12062
12196
  column: location.column,
12063
- size: location.size || 0,
12197
+ size: location.size,
12064
12198
  selector: () => null,
12065
12199
  });
12066
12200
  }
@@ -12490,12 +12624,12 @@ class FileSystemConfigLoader extends ConfigLoader {
12490
12624
  * `null` if no configuration files are found.
12491
12625
  */
12492
12626
  fromFilename(filename) {
12493
- var _a;
12494
12627
  if (filename === "inline") {
12495
12628
  return null;
12496
12629
  }
12497
- if (this.cache.has(filename)) {
12498
- return (_a = this.cache.get(filename)) !== null && _a !== void 0 ? _a : null;
12630
+ const cache = this.cache.get(filename);
12631
+ if (cache) {
12632
+ return cache;
12499
12633
  }
12500
12634
  let found = false;
12501
12635
  let current = path__default["default"].resolve(path__default["default"].dirname(filename));