chrome-devtools-frontend 1.0.996595 → 1.0.998045

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.
Files changed (34) hide show
  1. package/AUTHORS +1 -0
  2. package/README.md +2 -2
  3. package/docs/design_guidelines.md +1 -1
  4. package/front_end/core/i18n/locales/en-US.json +6 -99
  5. package/front_end/core/i18n/locales/en-XL.json +6 -99
  6. package/front_end/core/root/Runtime.ts +5 -0
  7. package/front_end/core/sdk/DebuggerModel.ts +1 -1
  8. package/front_end/core/sdk/NetworkManager.ts +1 -2
  9. package/front_end/entrypoints/main/MainImpl.ts +1 -1
  10. package/front_end/generated/InspectorBackendCommands.js +0 -1
  11. package/front_end/generated/protocol.ts +5 -17
  12. package/front_end/models/bindings/BreakpointManager.ts +27 -3
  13. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +9 -0
  14. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +13 -0
  15. package/front_end/models/issues_manager/DeprecationIssue.ts +376 -117
  16. package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +1 -1
  17. package/front_end/panels/css_overview/components/CSSOverviewStartView.ts +1 -1
  18. package/front_end/panels/css_overview/cssOverview.css +4 -0
  19. package/front_end/panels/elements/StylesSidebarPane.ts +19 -9
  20. package/front_end/panels/elements/components/adornerSettingsPane.css +5 -0
  21. package/front_end/panels/elements/stylesSectionTree.css +4 -4
  22. package/front_end/panels/sources/DebuggerPlugin.ts +6 -2
  23. package/front_end/panels/sources/SourcesPanel.ts +22 -6
  24. package/front_end/panels/sources/sources-legacy.ts +1 -1
  25. package/front_end/panels/sources/sources-meta.ts +61 -7
  26. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +1 -1
  27. package/front_end/ui/legacy/SplitWidget.ts +17 -7
  28. package/front_end/ui/legacy/themeColors.css +2 -0
  29. package/front_end/ui/legacy/toolbar.css +5 -1
  30. package/package.json +1 -1
  31. package/scripts/eslint_rules/lib/lit_template_result_or_nothing.js +140 -0
  32. package/scripts/eslint_rules/lib/no_only_eslint_tests.js +5 -1
  33. package/scripts/eslint_rules/tests/lit_template_result_or_nothing_test.js +133 -0
  34. package/scripts/hosted_mode/server.js +1 -1
@@ -78,7 +78,7 @@ export class AXBreadcrumbsPane extends AccessibilitySubPane {
78
78
  };
79
79
  if (Root.Runtime.experiments.isEnabled(experiment)) {
80
80
  this.#legacyTreeDisabled = true;
81
- const feedbackURL = 'https://goo.gle/devtools-a11y-tree-feedback';
81
+ const feedbackURL = 'https://g.co/devtools/a11y-tree-feedback';
82
82
  previewToggle.data = {
83
83
  name,
84
84
  helperText: i18nString(UIStrings.fullTreeExperimentDescription),
@@ -43,7 +43,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
43
43
 
44
44
  const {render, html} = LitHtml;
45
45
 
46
- const FEEDBACK_LINK = 'https://goo.gle/css-overview-feedback';
46
+ const FEEDBACK_LINK = 'https://g.co/devtools/css-overview-feedback';
47
47
  const DOC_LINK = 'https://developer.chrome.com/docs/devtools/css-overview';
48
48
  export class OverviewStartRequestedEvent extends Event {
49
49
  static readonly eventName = 'overviewstartrequested';
@@ -7,3 +7,7 @@
7
7
  .css-overview-panel {
8
8
  overflow: hidden;
9
9
  }
10
+
11
+ devtools-css-overview-start-view {
12
+ overflow: auto;
13
+ }
@@ -871,22 +871,32 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
871
871
  };
872
872
  });
873
873
 
874
- let pseudoTypes: Protocol.DOM.PseudoType[] = [];
875
- const keys = matchedStyles.pseudoTypes();
876
- if (keys.delete(Protocol.DOM.PseudoType.Before)) {
877
- pseudoTypes.push(Protocol.DOM.PseudoType.Before);
878
- }
879
- pseudoTypes = pseudoTypes.concat([...keys].sort());
880
-
881
874
  const otherPseudoRulesets: {
882
875
  highlightName: string|null,
883
876
  pseudoType: Protocol.DOM.PseudoType,
884
877
  pseudoStyles: SDK.CSSStyleDeclaration.CSSStyleDeclaration[],
885
- }[] = pseudoTypes.map(pseudoType => {
878
+ }[] = [...matchedStyles.pseudoTypes()].map(pseudoType => {
886
879
  return {'highlightName': null, 'pseudoType': pseudoType, 'pseudoStyles': matchedStyles.pseudoStyles(pseudoType)};
887
880
  });
888
881
 
889
- const pseudoRulesets = customHighlightPseudoRulesets.concat(otherPseudoRulesets);
882
+ const pseudoRulesets = customHighlightPseudoRulesets.concat(otherPseudoRulesets).sort((a, b) => {
883
+ // We want to show the ::before pseudos first, followed by the remaining pseudos
884
+ // in alphabetical order.
885
+ if (a.pseudoType === Protocol.DOM.PseudoType.Before && b.pseudoType !== Protocol.DOM.PseudoType.Before) {
886
+ return -1;
887
+ }
888
+ if (a.pseudoType !== Protocol.DOM.PseudoType.Before && b.pseudoType === Protocol.DOM.PseudoType.Before) {
889
+ return 1;
890
+ }
891
+ if (a.pseudoType < b.pseudoType) {
892
+ return -1;
893
+ }
894
+ if (a.pseudoType > b.pseudoType) {
895
+ return 1;
896
+ }
897
+ return 0;
898
+ });
899
+
890
900
  for (const pseudo of pseudoRulesets) {
891
901
  lastParentNode = null;
892
902
  for (let i = 0; i < pseudo.pseudoStyles.length; ++i) {
@@ -57,6 +57,11 @@
57
57
  border-radius: 2px;
58
58
  }
59
59
 
60
+ .close:hover,
61
+ .close:focus {
62
+ background-color: var(--color-background-elevation-2);
63
+ }
64
+
60
65
  .close::before {
61
66
  transform: rotate(45deg);
62
67
  }
@@ -126,6 +126,10 @@ ol.expanded {
126
126
  opacity: 50%;
127
127
  }
128
128
 
129
+ .changed {
130
+ background-color: var(--color-accent-green-background);
131
+ }
132
+
129
133
  .changed::after {
130
134
  content: "";
131
135
  position: absolute;
@@ -140,10 +144,6 @@ ol.expanded {
140
144
  display: none;
141
145
  }
142
146
 
143
- .changed:hover {
144
- background-color: var(--color-accent-green-background);
145
- }
146
-
147
147
  .changed:hover .copy {
148
148
  position: absolute;
149
149
  right: -4px;
@@ -536,6 +536,10 @@ export class DebuggerPlugin extends Plugin {
536
536
  return true;
537
537
  }
538
538
 
539
+ private isVariableIdentifier(tokenType: string): boolean {
540
+ return tokenType === 'VariableName' || tokenType === 'VariableDefinition';
541
+ }
542
+
539
543
  private isIdentifier(tokenType: string): boolean {
540
544
  return tokenType === 'VariableName' || tokenType === 'VariableDefinition' || tokenType === 'PropertyName' ||
541
545
  tokenType === 'PropertyDefinition';
@@ -962,8 +966,8 @@ export class DebuggerPlugin extends Plugin {
962
966
  tree.iterate({
963
967
  from: fromPos,
964
968
  to: toPos,
965
- enter(node): void {
966
- const varName = node.name === 'VariableName' && editorState.sliceDoc(node.from, node.to);
969
+ enter: node => {
970
+ const varName = this.isVariableIdentifier(node.name) && editorState.sliceDoc(node.from, node.to);
967
971
  if (varName && variableMap.has(varName)) {
968
972
  if (node.from > curLine.to) {
969
973
  curLine = editorState.doc.lineAt(node.from);
@@ -439,6 +439,14 @@ export class SourcesPanel extends UI.Panel.Panel implements UI.ContextMenu.Provi
439
439
  return this.sourcesViewInternal.searchableView();
440
440
  }
441
441
 
442
+ toggleNavigatorSidebar(): void {
443
+ this.editorView.toggleSidebar();
444
+ }
445
+
446
+ toggleDebuggerSidebar(): void {
447
+ this.splitWidget.toggleSidebar();
448
+ }
449
+
442
450
  private debuggerPaused(event: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.DebuggerModel>): void {
443
451
  const debuggerModel = event.data;
444
452
  const details = debuggerModel.debuggerPausedDetails();
@@ -1285,18 +1293,18 @@ export class RevealingActionDelegate implements UI.ActionRegistration.ActionDele
1285
1293
  }
1286
1294
  }
1287
1295
 
1288
- let debuggingActionDelegateInstance: DebuggingActionDelegate;
1296
+ let actionDelegateInstance: ActionDelegate;
1289
1297
 
1290
- export class DebuggingActionDelegate implements UI.ActionRegistration.ActionDelegate {
1298
+ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
1291
1299
  static instance(opts: {
1292
1300
  forceNew: boolean|null,
1293
- } = {forceNew: null}): DebuggingActionDelegate {
1301
+ } = {forceNew: null}): ActionDelegate {
1294
1302
  const {forceNew} = opts;
1295
- if (!debuggingActionDelegateInstance || forceNew) {
1296
- debuggingActionDelegateInstance = new DebuggingActionDelegate();
1303
+ if (!actionDelegateInstance || forceNew) {
1304
+ actionDelegateInstance = new ActionDelegate();
1297
1305
  }
1298
1306
 
1299
- return debuggingActionDelegateInstance;
1307
+ return actionDelegateInstance;
1300
1308
  }
1301
1309
  handleAction(context: UI.Context.Context, actionId: string): boolean {
1302
1310
  const panel = SourcesPanel.instance();
@@ -1340,6 +1348,14 @@ export class DebuggingActionDelegate implements UI.ActionRegistration.ActionDele
1340
1348
  }
1341
1349
  return true;
1342
1350
  }
1351
+ case 'sources.toggle-navigator-sidebar': {
1352
+ panel.toggleNavigatorSidebar();
1353
+ return true;
1354
+ }
1355
+ case 'sources.toggle-debugger-sidebar': {
1356
+ panel.toggleDebuggerSidebar();
1357
+ return true;
1358
+ }
1343
1359
  }
1344
1360
  return false;
1345
1361
  }
@@ -189,7 +189,7 @@ Sources.SourcesPanel.DebuggerPausedDetailsRevealer = SourcesModule.SourcesPanel.
189
189
  Sources.SourcesPanel.RevealingActionDelegate = SourcesModule.SourcesPanel.RevealingActionDelegate;
190
190
 
191
191
  /** @constructor */
192
- Sources.SourcesPanel.DebuggingActionDelegate = SourcesModule.SourcesPanel.DebuggingActionDelegate;
192
+ Sources.SourcesPanel.ActionDelegate = SourcesModule.SourcesPanel.ActionDelegate;
193
193
 
194
194
  /** @constructor */
195
195
  Sources.SourcesPanel.WrapperView = SourcesModule.SourcesPanel.WrapperView;
@@ -378,6 +378,14 @@ const UIStrings = {
378
378
  * comes to a halt.
379
379
  */
380
380
  enableAutoFocusOnDebuggerPaused: 'Focus Sources panel when triggering a breakpoint',
381
+ /**
382
+ * @description Text for command of toggling navigator sidebar in Sources panel
383
+ */
384
+ toggleNavigatorSidebar: 'Toggle navigator sidebar',
385
+ /**
386
+ * @description Text for command of toggling debugger sidebar in Sources panel
387
+ */
388
+ toggleDebuggerSidebar: 'Toggle debugger sidebar',
381
389
 
382
390
  };
383
391
  const str_ = i18n.i18n.registerUIStrings('panels/sources/sources-meta.ts', UIStrings);
@@ -567,7 +575,7 @@ UI.ActionRegistration.registerActionExtension({
567
575
  actionId: 'debugger.step-over',
568
576
  async loadActionDelegate() {
569
577
  const Sources = await loadSourcesModule();
570
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
578
+ return Sources.SourcesPanel.ActionDelegate.instance();
571
579
  },
572
580
 
573
581
  title: i18nLazyString(UIStrings.stepOverNextFunctionCall),
@@ -599,7 +607,7 @@ UI.ActionRegistration.registerActionExtension({
599
607
  actionId: 'debugger.step-into',
600
608
  async loadActionDelegate() {
601
609
  const Sources = await loadSourcesModule();
602
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
610
+ return Sources.SourcesPanel.ActionDelegate.instance();
603
611
  },
604
612
  title: i18nLazyString(UIStrings.stepIntoNextFunctionCall),
605
613
  iconClass: UI.ActionRegistration.IconClass.LARGE_ICON_STEP_INTO,
@@ -630,7 +638,7 @@ UI.ActionRegistration.registerActionExtension({
630
638
  actionId: 'debugger.step',
631
639
  async loadActionDelegate() {
632
640
  const Sources = await loadSourcesModule();
633
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
641
+ return Sources.SourcesPanel.ActionDelegate.instance();
634
642
  },
635
643
  title: i18nLazyString(UIStrings.step),
636
644
  iconClass: UI.ActionRegistration.IconClass.LARGE_ICON_STEP,
@@ -652,7 +660,7 @@ UI.ActionRegistration.registerActionExtension({
652
660
  actionId: 'debugger.step-out',
653
661
  async loadActionDelegate() {
654
662
  const Sources = await loadSourcesModule();
655
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
663
+ return Sources.SourcesPanel.ActionDelegate.instance();
656
664
  },
657
665
  title: i18nLazyString(UIStrings.stepOutOfCurrentFunction),
658
666
  iconClass: UI.ActionRegistration.IconClass.LARGE_ICON_STEP_OUT,
@@ -683,7 +691,7 @@ UI.ActionRegistration.registerActionExtension({
683
691
  category: UI.ActionRegistration.ActionCategory.DEBUGGER,
684
692
  async loadActionDelegate() {
685
693
  const Sources = await loadSourcesModule();
686
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
694
+ return Sources.SourcesPanel.ActionDelegate.instance();
687
695
  },
688
696
  title: i18nLazyString(UIStrings.runSnippet),
689
697
  iconClass: UI.ActionRegistration.IconClass.LARGEICON_PLAY,
@@ -709,7 +717,7 @@ UI.ActionRegistration.registerActionExtension({
709
717
  toggleable: true,
710
718
  async loadActionDelegate() {
711
719
  const Sources = await loadSourcesModule();
712
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
720
+ return Sources.SourcesPanel.ActionDelegate.instance();
713
721
  },
714
722
  contextTypes() {
715
723
  return maybeRetrieveContextTypes(Sources => [Sources.SourcesView.SourcesView]);
@@ -764,7 +772,7 @@ UI.ActionRegistration.registerActionExtension({
764
772
  category: UI.ActionRegistration.ActionCategory.DEBUGGER,
765
773
  async loadActionDelegate() {
766
774
  const Sources = await loadSourcesModule();
767
- return Sources.SourcesPanel.DebuggingActionDelegate.instance();
775
+ return Sources.SourcesPanel.ActionDelegate.instance();
768
776
  },
769
777
  title: i18nLazyString(UIStrings.evaluateSelectedTextInConsole),
770
778
  contextTypes() {
@@ -1240,6 +1248,52 @@ UI.ActionRegistration.registerActionExtension({
1240
1248
  ],
1241
1249
  });
1242
1250
 
1251
+ UI.ActionRegistration.registerActionExtension({
1252
+ actionId: 'sources.toggle-navigator-sidebar',
1253
+ category: UI.ActionRegistration.ActionCategory.SOURCES,
1254
+ title: i18nLazyString(UIStrings.toggleNavigatorSidebar),
1255
+ async loadActionDelegate() {
1256
+ const Sources = await loadSourcesModule();
1257
+ return Sources.SourcesPanel.ActionDelegate.instance();
1258
+ },
1259
+ contextTypes() {
1260
+ return maybeRetrieveContextTypes(Sources => [Sources.SourcesView.SourcesView]);
1261
+ },
1262
+ bindings: [
1263
+ {
1264
+ platform: UI.ActionRegistration.Platforms.WindowsLinux,
1265
+ shortcut: 'Ctrl+Shift+y',
1266
+ },
1267
+ {
1268
+ platform: UI.ActionRegistration.Platforms.Mac,
1269
+ shortcut: 'Meta+Shift+y',
1270
+ },
1271
+ ],
1272
+ });
1273
+
1274
+ UI.ActionRegistration.registerActionExtension({
1275
+ actionId: 'sources.toggle-debugger-sidebar',
1276
+ category: UI.ActionRegistration.ActionCategory.SOURCES,
1277
+ title: i18nLazyString(UIStrings.toggleDebuggerSidebar),
1278
+ async loadActionDelegate() {
1279
+ const Sources = await loadSourcesModule();
1280
+ return Sources.SourcesPanel.ActionDelegate.instance();
1281
+ },
1282
+ contextTypes() {
1283
+ return maybeRetrieveContextTypes(Sources => [Sources.SourcesView.SourcesView]);
1284
+ },
1285
+ bindings: [
1286
+ {
1287
+ platform: UI.ActionRegistration.Platforms.WindowsLinux,
1288
+ shortcut: 'Ctrl+Shift+h',
1289
+ },
1290
+ {
1291
+ platform: UI.ActionRegistration.Platforms.Mac,
1292
+ shortcut: 'Meta+Shift+h',
1293
+ },
1294
+ ],
1295
+ });
1296
+
1243
1297
  Common.Settings.registerSettingExtension({
1244
1298
  settingName: 'navigatorGroupByFolder',
1245
1299
  settingType: Common.Settings.SettingType.BOOLEAN,
@@ -29,7 +29,7 @@ export const markdownLinks = new Map<string, string>([
29
29
  ['sameSiteAndSameOrigin', 'https://web.dev/same-site-same-origin/'],
30
30
  // Link URLs for deprecation issues (see blink::Deprecation)
31
31
  ['https://xhr.spec.whatwg.org/', 'https://xhr.spec.whatwg.org/'],
32
- ['https://goo.gl/rStTGz', 'https://goo.gl/rStTGz'],
32
+ ['https://goo.gle/chrome-insecure-origins', 'https://goo.gle/chrome-insecure-origins'],
33
33
  ['https://webrtc.org/web-apis/chrome/unified-plan/', 'https://webrtc.org/web-apis/chrome/unified-plan/'],
34
34
  [
35
35
  'https://developer.chrome.com/blog/enabling-shared-array-buffer/',
@@ -61,6 +61,8 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
61
61
  private animationCallback: (() => void)|null;
62
62
  private showSidebarButtonTitle: Common.UIString.LocalizedString;
63
63
  private hideSidebarButtonTitle: Common.UIString.LocalizedString;
64
+ private shownSidebarString: Common.UIString.LocalizedString;
65
+ private hiddenSidebarString: Common.UIString.LocalizedString;
64
66
  private showHideSidebarButton: ToolbarButton|null;
65
67
  private isVerticalInternal: boolean;
66
68
  private sidebarMinimized: boolean;
@@ -111,6 +113,8 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
111
113
  this.animationCallback = null;
112
114
  this.showSidebarButtonTitle = Common.UIString.LocalizedEmptyString;
113
115
  this.hideSidebarButtonTitle = Common.UIString.LocalizedEmptyString;
116
+ this.shownSidebarString = Common.UIString.LocalizedEmptyString;
117
+ this.hiddenSidebarString = Common.UIString.LocalizedEmptyString;
114
118
  this.showHideSidebarButton = null;
115
119
  this.isVerticalInternal = false;
116
120
  this.sidebarMinimized = false;
@@ -821,23 +825,29 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
821
825
  shownString: Common.UIString.LocalizedString, hiddenString: Common.UIString.LocalizedString): ToolbarButton {
822
826
  this.showSidebarButtonTitle = showTitle;
823
827
  this.hideSidebarButtonTitle = hideTitle;
828
+ this.shownSidebarString = shownString;
829
+ this.hiddenSidebarString = hiddenString;
824
830
  this.showHideSidebarButton = new ToolbarButton('', '');
825
831
  this.showHideSidebarButton.addEventListener(ToolbarButton.Events.Click, buttonClicked, this);
826
832
  this.updateShowHideSidebarButton();
827
833
 
828
834
  function buttonClicked(this: SplitWidget): void {
829
- if (this.showModeInternal !== ShowMode.Both) {
830
- this.showBoth(true);
831
- ARIAUtils.alert(shownString);
832
- } else {
833
- this.hideSidebar(true);
834
- ARIAUtils.alert(hiddenString);
835
- }
835
+ this.toggleSidebar();
836
836
  }
837
837
 
838
838
  return this.showHideSidebarButton;
839
839
  }
840
840
 
841
+ toggleSidebar(): void {
842
+ if (this.showModeInternal !== ShowMode.Both) {
843
+ this.showBoth(true);
844
+ ARIAUtils.alert(this.shownSidebarString);
845
+ } else {
846
+ this.hideSidebar(true);
847
+ ARIAUtils.alert(this.hiddenSidebarString);
848
+ }
849
+ }
850
+
841
851
  private updateShowHideSidebarButton(): void {
842
852
  if (!this.showHideSidebarButton) {
843
853
  return;
@@ -124,6 +124,7 @@
124
124
  --color-button-primary-background-hovering: rgb(77 134 225 / 100%);
125
125
  --color-button-primary-background-pressed: rgb(88 132 205);
126
126
  --color-button-primary-text: rgb(255 255 255);
127
+ --color-button-primary-text-hover: rgb(218 220 224);
127
128
  --color-button-secondary-background-hovering: rgb(26 115 232 / 10%);
128
129
  --color-button-secondary-background-pressed: rgb(26 92 178 / 25%);
129
130
  --color-button-secondary-border: rgb(218 220 224);
@@ -272,6 +273,7 @@
272
273
  --color-button-primary-background-hovering: rgb(174 203 250 / 100%);
273
274
  --color-button-primary-background-pressed: rgb(210 227 252 / 100%);
274
275
  --color-button-primary-text: rgb(0 0 0);
276
+ --color-button-primary-text-hover: rgb(60 61 65);
275
277
  --color-button-secondary-background-hovering: rgb(138 180 248 / 15%);
276
278
  --color-button-secondary-background-pressed: rgb(138 180 248 / 23%);
277
279
  --color-button-secondary-border: rgb(60 61 65);
@@ -459,10 +459,14 @@ input[is="history-input"]:not(:placeholder-shown) {
459
459
  z-index: -1;
460
460
  }
461
461
 
462
- .toolbar-item.highlight {
462
+ .toolbar-item.highlight > .title {
463
463
  color: var(--color-button-primary-text);
464
464
  }
465
465
 
466
+ .toolbar-item.highlight:hover:not(:active) > .title {
467
+ color: var(--color-button-primary-text-hover);
468
+ }
469
+
466
470
  .toolbar-item.highlight:focus-visible {
467
471
  background: var(--color-primary-variant);
468
472
  }
package/package.json CHANGED
@@ -55,5 +55,5 @@
55
55
  "unittest": "scripts/test/run_unittests.py --no-text-coverage",
56
56
  "watch": "vpython third_party/node/node.py --output scripts/watch_build.js"
57
57
  },
58
- "version": "1.0.996595"
58
+ "version": "1.0.998045"
59
59
  }
@@ -0,0 +1,140 @@
1
+ // Copyright 2020 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+ 'use strict';
5
+
6
+ module.exports = {
7
+ meta: {
8
+ type: 'problem',
9
+
10
+ docs: {
11
+ description: 'Enforce use of LitHtml.LitTemplate type rather than union with LitHtml.nothing',
12
+ category: 'Possible Errors',
13
+ },
14
+ fixable: 'code',
15
+ messages: {
16
+ useLitTemplateOverEmptyObject:
17
+ 'Prefer LitHtml.LitTemplate type over a union with {}',
18
+ useLitTemplateOverTypeOfNothing:
19
+ 'Prefer LitHtml.LitTemplate type over a union with LitHtml.nothing',
20
+ },
21
+ schema: [] // no options
22
+ },
23
+ create: function(context) {
24
+ const sourceCode = context.getSourceCode();
25
+ const UNION_TYPE_FOR_LIT_TEMPLATE = 'LitHtml.LitTemplate';
26
+
27
+ function checkUnionReturnTypeForLit(node) {
28
+ // We want to go through the types in the union and match if:
29
+ // 1. We find `LitHtml.TemplateResult` and `{}`
30
+ // 2. We find `LitHtml.TemplateResult` and `LitHtml.nothing`.
31
+ // Otherwise, this node is OK.
32
+
33
+ let templateResultNode = null;
34
+ let literalEmptyObjectNode = null;
35
+ let litNothingNode = null;
36
+
37
+ const nonLitRelatedNodesInUnion = new Set();
38
+
39
+ for (const typeNode of node.types) {
40
+ // This matches a type reference of X.y. Now we see if X === 'LitHtml' and y === 'TemplateResult'
41
+ if (typeNode.type === 'TSTypeReference' && typeNode.typeName.type === 'TSQualifiedName') {
42
+ const leftText = typeNode.typeName.left.name;
43
+ const rightText = typeNode.typeName.right.name;
44
+ if (leftText === 'LitHtml' && rightText === 'TemplateResult') {
45
+ templateResultNode = typeNode;
46
+ continue;
47
+ }
48
+ } else if (typeNode.type === 'TSTypeLiteral') {
49
+ // The TSTypeLiteral type matches against the literal `{}` type.
50
+ literalEmptyObjectNode = typeNode;
51
+ continue;
52
+ } else if (typeNode.type === 'TSTypeQuery' && typeNode.exprName.type === 'TSQualifiedName') {
53
+ // matches `typeof X.y`
54
+ const leftText = typeNode.exprName.left.name;
55
+ const rightText = typeNode.exprName.right.name;
56
+ if (leftText === 'LitHtml' && rightText === 'nothing') {
57
+ litNothingNode = typeNode;
58
+ continue;
59
+ }
60
+ }
61
+
62
+ nonLitRelatedNodesInUnion.add(typeNode);
63
+ }
64
+
65
+ // We didn't find LitHtml.TemplateResult, so bail.
66
+ if (!templateResultNode) {
67
+ return;
68
+ }
69
+
70
+ if (!litNothingNode && !literalEmptyObjectNode) {
71
+ // We found TemplateResult with no `typeof LitHtml.nothing` or `{}`, so
72
+ // bail.
73
+ return;
74
+ }
75
+
76
+ // If we found a union type of:
77
+ // LitHtml.TemplateResult|{}|number
78
+ // That needs to become:
79
+ // LitHtml.LitTemplate|number
80
+ // So we capture all the non-lit related types in the union, and get
81
+ // their text content, so we can keep them around when we run the fixer.
82
+ const nonLitRelatedTypesToKeep = Array.from(nonLitRelatedNodesInUnion, node => {
83
+ return sourceCode.getText(node);
84
+ });
85
+ const newText = [UNION_TYPE_FOR_LIT_TEMPLATE, ...nonLitRelatedTypesToKeep].join('|');
86
+
87
+ context.report({
88
+ node,
89
+ messageId: litNothingNode ? 'useLitTemplateOverTypeOfNothing' : 'useLitTemplateOverEmptyObject',
90
+ fix(fixer) {
91
+ return fixer.replaceText(node, newText);
92
+ }
93
+ });
94
+ }
95
+
96
+ function checkTSTypeAnnotationForPotentialIssue(node) {
97
+ const annotation = node.typeAnnotation;
98
+ if (annotation.type === 'TSUnionType') {
99
+ // matches foo(): X|Y
100
+ checkUnionReturnTypeForLit(annotation);
101
+ } else if (annotation.type === 'TSTypeReference') {
102
+ // matches many things, including foo(): Promise<X|Y>, which we do want
103
+ // to check.
104
+
105
+ if (annotation.typeName.name !== 'Promise') {
106
+ // If it's not a promise, bail out.
107
+ return;
108
+ }
109
+ // Represents the generic type passed to the promise: if our code is
110
+ // Promise<X>, this node represents the X.
111
+ const promiseGenericNode = annotation.typeParameters.params[0];
112
+ if (promiseGenericNode && promiseGenericNode.type === 'TSUnionType') {
113
+ checkUnionReturnTypeForLit(promiseGenericNode);
114
+ }
115
+ }
116
+ }
117
+
118
+ function checkFunctionDeclarationOrExpressionForUnionType(node) {
119
+ if (node.returnType.type !== 'TSTypeAnnotation') {
120
+ return;
121
+ }
122
+ checkTSTypeAnnotationForPotentialIssue(node.returnType);
123
+ }
124
+
125
+ return {
126
+ // function() {}
127
+ FunctionDeclaration(node) {
128
+ checkFunctionDeclarationOrExpressionForUnionType(node);
129
+ },
130
+ // Match functions defined inside classes
131
+ FunctionExpression(node) {
132
+ checkFunctionDeclarationOrExpressionForUnionType(node);
133
+ },
134
+ // Match values in interfaces or types.
135
+ TSPropertySignature(node) {
136
+ checkTSTypeAnnotationForPotentialIssue(node.typeAnnotation);
137
+ },
138
+ };
139
+ }
140
+ };
@@ -17,6 +17,10 @@ module.exports = {
17
17
  create: function(context) {
18
18
  function checkForOnlyInTestCases(testCaseObjects) {
19
19
  for (const testCase of testCaseObjects) {
20
+ if (!testCase || !testCase.properties) {
21
+ continue;
22
+ }
23
+
20
24
  const onlyKeyProp = testCase.properties.find(prop => {
21
25
  return prop.key.name === 'only';
22
26
  });
@@ -37,7 +41,7 @@ module.exports = {
37
41
  // second argument = rule itself
38
42
  // third argument = the object containing the test cases - what we want!
39
43
  const tests = node.arguments[2];
40
- if (!tests) {
44
+ if (!tests || !tests.properties) {
41
45
  return;
42
46
  }
43
47