16 | <_li>The build system (Meson based)
17 | <_li>Configuring and using GSettings
18 | <_li>Using GResources for source and data
19 | <_li>Desktop file and appdata
20 |
21 |
22 |
23 | https://www.example.com/gtk-js-app/screenshots/large.png
24 |
25 | https://www.example.com/gtk-js-app
26 | gcampagna_at_src.gnome.org
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | About My JS Application
2 | =======================
3 |
4 | My JS Application, as the name suggests, is nothing but an
5 | application written in JS. It installs, and it runs.
6 | It's meant as an example of the GNOME application platform,
7 | and in particular Gtk and the Gjs package system.
8 |
9 | It should also work as a template for developing a real
10 | application, ie. one that does something, without spending
11 | all the time doing build system configuration.
12 |
13 | Features
14 | ========
15 |
16 | My JS Application most of the familiar UI you'd expect
17 | from a core application: it has a main window with a header
18 | bar, it has a search bar and it has multiple page stack.
19 |
20 | On the developer side, the most prominent feature is
21 | that My JS Application runs uninstalled with a special
22 | Meson target. You should be able to run
23 | ```
24 | meson _build
25 | meson compile -C _build devel
26 | ```
27 | and everything should just work, without having to run `ninja install`.
28 |
29 | Also, it features an util module, which deals with
30 | GtkBuilder and GtkCssProvider, again providing
31 | transparency between the installed and not installed cases.
32 |
33 | License
34 | =======
35 |
36 | The package is under a 3-clause BSD license, to
37 | make it suitable for inclusion in any application.
38 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Giovanni Campagna
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 | * Redistributions of source code must retain the above copyright
6 | notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright
8 | notice, this list of conditions and the following disclaimer in the
9 | documentation and/or other materials provided with the distribution.
10 | * Neither the name of the GNOME Foundation nor the
11 | names of its contributors may be used to endorse or promote products
12 | derived from this software without specific prior written permission.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 |
--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
1 | data_resource = gnome.compile_resources(app_id + '.data',
2 | app_id + '.data.gresource.xml',
3 | source_dir: '.',
4 | gresource_bundle: true,
5 | install: true,
6 | install_dir : pkgdatadir)
7 |
8 | appsdir = join_paths(get_option('datadir'), 'applications')
9 | desktop = intl.merge_file(
10 | input : app_id + '.desktop.in',
11 | output : app_id + '.desktop',
12 | po_dir : '../po',
13 | type : 'desktop',
14 | install: true,
15 | install_dir: appsdir
16 | )
17 |
18 | gsettingsdir = join_paths(get_option('datadir'), 'glib-2.0', 'schemas')
19 | gsettings_schema = app_id + '.gschema.xml'
20 | install_data(gsettings_schema, install_dir : gsettingsdir)
21 | meson.add_install_script('../meson/meson_post_install.py')
22 |
23 | local_schemas = configure_file(copy: true,
24 | input: gsettings_schema,
25 | output: gsettings_schema)
26 | compile_local_schemas = custom_target('compile_local_schemas',
27 | input: local_schemas,
28 | output: 'gschemas.compiled',
29 | command: ['glib-compile-schemas', meson.current_build_dir()])
30 |
31 | appdatadir = join_paths(get_option('datadir'), 'appdata')
32 | appdata = intl.merge_file(
33 | input : app_id + '.appdata.xml.in',
34 | output : app_id + '.appdata.xml',
35 | po_dir : '../po',
36 | type : 'xml',
37 | install: true,
38 | install_dir: appdatadir
39 | )
40 |
41 | dbusservicedir = join_paths(get_option('datadir'), 'dbus-1', 'services')
42 | dbus_service = configure_file(
43 | configuration : app_configuration,
44 | input : app_id + '.service.in',
45 | output : app_id + '.service',
46 | install : true,
47 | install_dir : dbusservicedir
48 | )
49 |
--------------------------------------------------------------------------------
/tests/testutil.py:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 |
3 | from gi.repository import GLib, Gio
4 |
5 | from dogtail.utils import isA11yEnabled, enableA11y
6 | if not isA11yEnabled():
7 | enableA11y(True)
8 |
9 | from dogtail import tree
10 | from dogtail import utils
11 | from dogtail.predicate import *
12 | from dogtail.procedural import *
13 |
14 | import os, sys
15 | import subprocess
16 |
17 | APPLICATION_ID = "com.example.Gtk.JSApplication"
18 |
19 | _bus = None
20 |
21 | class IsTextEqual(Predicate):
22 | """Predicate subclass that looks for top-level windows"""
23 | def __init__(self, text):
24 | self.text = text
25 |
26 | def satisfiedByNode(self, node):
27 | try:
28 | textIface = node.queryText()
29 | #print textIface.getText(0, -1)
30 | return textIface.getText(0, -1) == self.text
31 | except NotImplementedError:
32 | return False
33 |
34 | def describeSearchResult(self):
35 | return '%s text node' % self.text
36 |
37 | def _do_bus_call(method, params):
38 | global _bus
39 |
40 | if _bus == None:
41 | _bus = Gio.bus_get_sync(Gio.BusType.SESSION)
42 | _bus.call_sync(APPLICATION_ID, '/' + APPLICATION_ID.replace('.', '/'),
43 | 'org.freedesktop.Application',
44 | method, params, None,
45 | Gio.DBusCallFlags.NONE,
46 | -1, None)
47 |
48 | def start():
49 | builddir = os.environ.get('G_TEST_BUILDDIR', None)
50 | if builddir and not 'TESTUTIL_DONT_START' in os.environ:
51 | subprocess.Popen([os.path.join(builddir, '..', 'src', APPLICATION_ID)],
52 | cwd=os.path.join(builddir, '..'))
53 | else:
54 | _do_bus_call("Activate", GLib.Variant('(a{sv})', ([],)))
55 | utils.doDelay(3)
56 |
57 | app = tree.root.application(APPLICATION_ID)
58 | focus.application(APPLICATION_ID)
59 |
60 | return app
61 |
62 | def init():
63 | pass
64 | def fini():
65 | _do_bus_call("ActivateAction", GLib.Variant('(sava{sv})', ('quit', [], [])))
66 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
2 | // SPDX-License-Identifier: BSD-3-Clause
3 | // SPDX-FileCopyrightText: 2013 Giovanni Campagna
4 |
5 | /* exported main */
6 |
7 | pkg.initGettext();
8 | pkg.require({
9 | 'Gdk': '3.0',
10 | 'Gio': '2.0',
11 | 'GLib': '2.0',
12 | 'GObject': '2.0',
13 | 'Gtk': '3.0',
14 | });
15 |
16 | const Gio = imports.gi.Gio;
17 | const GLib = imports.gi.GLib;
18 | const GObject = imports.gi.GObject;
19 | const Gtk = imports.gi.Gtk;
20 |
21 | const Util = imports.util;
22 | const Window = imports.window;
23 |
24 | function initEnvironment() {
25 | globalThis.getApp = function () {
26 | return Gio.Application.get_default();
27 | };
28 | }
29 |
30 | const MyApplication = GObject.registerClass(class MyApplication extends Gtk.Application {
31 | _init() {
32 | super._init({application_id: pkg.name});
33 |
34 | GLib.set_application_name(_("My JS Application"));
35 | }
36 |
37 | _onQuit() {
38 | this.quit();
39 | }
40 |
41 | _initAppMenu() {
42 | let builder = new Gtk.Builder();
43 | builder.add_from_resource('/com/example/Gtk/JSApplication/app-menu.ui');
44 |
45 | let menu = builder.get_object('app-menu');
46 | this.set_app_menu(menu);
47 | }
48 |
49 | vfunc_startup() {
50 | super.vfunc_startup();
51 |
52 | Util.loadStyleSheet('/com/example/Gtk/JSApplication/application.css');
53 |
54 | Util.initActions(this, [{
55 | name: 'quit',
56 | activate: this._onQuit,
57 | }]);
58 | this._initAppMenu();
59 |
60 | log(_("My JS Application started"));
61 | }
62 |
63 | vfunc_activate() {
64 | new Window.MainWindow({application: this}).show();
65 | }
66 |
67 | vfunc_shutdown() {
68 | log(_("My JS Application exiting"));
69 |
70 | super.vfunc_shutdown();
71 | }
72 | });
73 |
74 | function main(argv) {
75 | initEnvironment();
76 |
77 | return new MyApplication().run(argv);
78 | }
79 |
--------------------------------------------------------------------------------
/data/main.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
30 |
31 |
32 |
33 | False
34 | True
35 | vertical
36 |
37 |
38 | True
39 | True
40 |
41 |
42 | True
43 | 500
44 | center
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
2 | // SPDX-License-Identifier: BSD-3-Clause
3 | // SPDX-FileCopyrightText: 2013 Giovanni Campagna
4 |
5 | /* exported arrayEqual, getSettings, initActions, loadStyleSheet, loadIcon,
6 | loadUI */
7 |
8 | const Gdk = imports.gi.Gdk;
9 | const Gio = imports.gi.Gio;
10 | const Gtk = imports.gi.Gtk;
11 | const System = imports.system;
12 |
13 | function loadUI(resourcePath, objects) {
14 | let ui = new Gtk.Builder();
15 |
16 | if (objects) {
17 | for (let o in objects)
18 | ui.expose_object(o, objects[o]);
19 | }
20 |
21 | ui.add_from_resource(resourcePath);
22 | return ui;
23 | }
24 |
25 | function loadStyleSheet(resource) {
26 | let provider = new Gtk.CssProvider();
27 | provider.load_from_file(Gio.File.new_for_uri(`resource://${resource}`));
28 | Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
29 | provider,
30 | Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
31 | }
32 |
33 | function initActions(actionMap, simpleActionEntries, defaultContext = actionMap) {
34 | simpleActionEntries.forEach(function (entry) {
35 | const {
36 | activate = null,
37 | change_state = null,
38 | context = defaultContext,
39 | ...params
40 | } = entry;
41 | const action = new Gio.SimpleAction(params);
42 |
43 | if (activate)
44 | action.connect('activate', activate.bind(context));
45 | if (change_state)
46 | action.connect('change-state', change_state.bind(context));
47 |
48 | actionMap.add_action(action);
49 | });
50 | }
51 |
52 | function arrayEqual(one, two) {
53 | if (one.length !== two.length)
54 | return false;
55 |
56 | for (let i = 0; i < one.length; i++) {
57 | if (one[i] !== two[i])
58 | return false;
59 | }
60 |
61 | return true;
62 | }
63 |
64 | function getSettings(schemaId, path) {
65 | const GioSSS = Gio.SettingsSchemaSource;
66 | let schemaSource;
67 |
68 | if (!pkg.moduledir.startsWith('resource://')) {
69 | // Running from the source tree
70 | schemaSource = GioSSS.new_from_directory(pkg.pkgdatadir,
71 | GioSSS.get_default(),
72 | false);
73 | } else {
74 | schemaSource = GioSSS.get_default();
75 | }
76 |
77 | let schemaObj = schemaSource.lookup(schemaId, true);
78 | if (!schemaObj) {
79 | log(`Missing GSettings schema ${schemaId}`);
80 | System.exit(1);
81 | }
82 |
83 | if (path === undefined) {
84 | return new Gio.Settings({settings_schema: schemaObj});
85 | } else {
86 | return new Gio.Settings({
87 | settings_schema: schemaObj,
88 | path,
89 | });
90 | }
91 | }
92 |
93 | function loadIcon(iconName, size) {
94 | let theme = Gtk.IconTheme.get_default();
95 |
96 | return theme.load_icon(iconName,
97 | size,
98 | Gtk.IconLookupFlags.GENERIC_FALLBACK);
99 | }
100 |
--------------------------------------------------------------------------------
/src/window.js:
--------------------------------------------------------------------------------
1 | // -*- Mode: js; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-
2 | // SPDX-License-Identifier: BSD-3-Clause
3 | // SPDX-FileCopyrightText: 2013 Giovanni Campagna
4 |
5 | /* exported MainWindow */
6 |
7 | const GLib = imports.gi.GLib;
8 | const GObject = imports.gi.GObject;
9 | const Gtk = imports.gi.Gtk;
10 |
11 | const Util = imports.util;
12 |
13 | var MainWindow = GObject.registerClass({
14 | Template: 'resource:///com/example/Gtk/JSApplication/main.ui',
15 | Children: ['main-grid', 'main-search-bar', 'main-search-entry',
16 | 'search-active-button'],
17 | Properties: {'search-active': GObject.ParamSpec.boolean('search-active', '', '', GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE, false)},
18 | }, class MainWindow extends Gtk.ApplicationWindow {
19 | _init(params) {
20 | super._init({
21 | title: GLib.get_application_name(),
22 | default_width: 640,
23 | default_height: 480,
24 | ...params,
25 | });
26 |
27 | this._searchActive = false;
28 |
29 | Util.initActions(this, [
30 | {
31 | name: 'new',
32 | activate: this._new,
33 | },
34 | {
35 | name: 'about',
36 | activate: this._about,
37 | },
38 | {
39 | name: 'search-active',
40 | activate: this._toggleSearch,
41 | parameter_type: new GLib.VariantType('b'),
42 | state: new GLib.Variant('b', false),
43 | },
44 | ]);
45 |
46 | this.bind_property('search-active', this.search_active_button, 'active',
47 | GObject.BindingFlags.SYNC_CREATE |
48 | GObject.BindingFlags.BIDIRECTIONAL);
49 | this.bind_property('search-active', this.main_search_bar, 'search-mode-enabled',
50 | GObject.BindingFlags.SYNC_CREATE |
51 | GObject.BindingFlags.BIDIRECTIONAL);
52 | this.main_search_bar.connect_entry(this.main_search_entry);
53 |
54 | this._view = new MainView();
55 | this._view.visible_child_name = Math.random() <= 0.5 ? 'one' : 'two';
56 | this.main_grid.add(this._view);
57 | this.main_grid.show_all();
58 |
59 | // Due to limitations of gobject-introspection wrt GdkEvent and GdkEventKey,
60 | // this needs to be a signal handler
61 | this.connect('key-press-event', this._handleKeyPress.bind(this));
62 | }
63 |
64 | get search_active() {
65 | return this._searchActive;
66 | }
67 |
68 | set search_active(v) {
69 | if (this._searchActive === v)
70 | return;
71 |
72 | this._searchActive = v;
73 | // do something with v
74 | this.notify('search-active');
75 | }
76 |
77 | _handleKeyPress(self, event) {
78 | return this.main_search_bar.handle_event(event);
79 | }
80 |
81 | _new() {
82 | log(_("New something"));
83 | }
84 |
85 | _about() {
86 | let aboutDialog = new Gtk.AboutDialog({
87 | authors: ['Giovanni Campagna '],
88 | translator_credits: _("translator-credits"),
89 | program_name: _("JS Application"),
90 | comments: _("Demo JS Application and template"),
91 | copyright: 'Copyright 2013 The gjs developers',
92 | license_type: Gtk.License.GPL_2_0,
93 | logo_icon_name: 'com.example.Gtk.JSApplication',
94 | version: pkg.version,
95 | website: 'http://www.example.com/gtk-js-app/',
96 | wrap_license: true,
97 | modal: true,
98 | transient_for: this,
99 | });
100 |
101 | aboutDialog.show();
102 | aboutDialog.connect('response', function () {
103 | aboutDialog.destroy();
104 | });
105 | }
106 | });
107 |
108 | const MainView = GObject.registerClass(class MainView extends Gtk.Stack {
109 | _init(params) {
110 | super._init({
111 | hexpand: true,
112 | vexpand: true,
113 | ...params,
114 | });
115 |
116 | this._settings = Util.getSettings(pkg.name);
117 |
118 | this._buttonOne = this._addPage('one', _("First page"), '');
119 | this._buttonOne.connect('clicked', () => {
120 | this.visible_child_name = 'two';
121 | });
122 | this._syncLabel();
123 | this._settings.connect('changed::show-exclamation-mark', this._syncLabel.bind(this));
124 |
125 | let two = this._addPage('two', _("Second page"), _("What did you expect?"));
126 | two.connect('clicked', () => {
127 | this.visible_child_name = 'one';
128 | });
129 | }
130 |
131 | _addPage(name, label, button) {
132 | let labelWidget = new Gtk.Label({label});
133 | labelWidget.get_style_context().add_class('big-label');
134 | let buttonWidget = new Gtk.Button({label: button});
135 |
136 | let grid = new Gtk.Grid({
137 | orientation: Gtk.Orientation.VERTICAL,
138 | halign: Gtk.Align.CENTER,
139 | valign: Gtk.Align.CENTER,
140 | });
141 | grid.add(labelWidget);
142 | grid.add(buttonWidget);
143 |
144 | this.add_named(grid, name);
145 | return buttonWidget;
146 | }
147 |
148 | _syncLabel() {
149 | if (this._settings.get_boolean('show-exclamation-mark'))
150 | this._buttonOne.label = _("Hello, world!");
151 | else
152 | this._buttonOne.label = _("Hello world");
153 | }
154 | });
155 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 | # SPDX-FileCopyrightText: 2018 Claudio André
4 | env:
5 | es6: true
6 | es2020: true
7 | extends: 'eslint:recommended'
8 | rules:
9 | array-bracket-newline:
10 | - error
11 | - consistent
12 | array-bracket-spacing:
13 | - error
14 | - never
15 | array-callback-return: error
16 | arrow-parens:
17 | - error
18 | - as-needed
19 | arrow-spacing: error
20 | block-scoped-var: error
21 | block-spacing: error
22 | brace-style: error
23 | # Waiting for this to have matured a bit in eslint
24 | # camelcase:
25 | # - error
26 | # - properties: never
27 | # allow: [^vfunc_, ^on_, _instance_init]
28 | comma-dangle:
29 | - error
30 | - arrays: always-multiline
31 | objects: always-multiline
32 | functions: never
33 | comma-spacing:
34 | - error
35 | - before: false
36 | after: true
37 | comma-style:
38 | - error
39 | - last
40 | computed-property-spacing: error
41 | curly:
42 | - error
43 | - multi-or-nest
44 | - consistent
45 | dot-location:
46 | - error
47 | - property
48 | eol-last: error
49 | eqeqeq: error
50 | func-call-spacing: error
51 | func-name-matching: error
52 | func-style:
53 | - error
54 | - declaration
55 | - allowArrowFunctions: true
56 | indent:
57 | - error
58 | - 4
59 | - ignoredNodes:
60 | # Allow not indenting the body of GObject.registerClass, since in the
61 | # future it's intended to be a decorator
62 | - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child'
63 | # Allow dedenting chained member expressions
64 | MemberExpression: 'off'
65 | key-spacing:
66 | - error
67 | - beforeColon: false
68 | afterColon: true
69 | keyword-spacing:
70 | - error
71 | - before: true
72 | after: true
73 | linebreak-style:
74 | - error
75 | - unix
76 | lines-between-class-members: error
77 | max-nested-callbacks: error
78 | max-statements-per-line: error
79 | new-parens: error
80 | no-array-constructor: error
81 | no-await-in-loop: error
82 | no-caller: error
83 | no-constant-condition:
84 | - error
85 | - checkLoops: false
86 | no-div-regex: error
87 | no-empty:
88 | - error
89 | - allowEmptyCatch: true
90 | no-extra-bind: error
91 | no-extra-parens:
92 | - error
93 | - all
94 | - conditionalAssign: false
95 | nestedBinaryExpressions: false
96 | returnAssign: false
97 | no-implicit-coercion:
98 | - error
99 | - allow:
100 | - '!!'
101 | no-invalid-this: error
102 | no-iterator: error
103 | no-label-var: error
104 | no-lonely-if: error
105 | no-loop-func: error
106 | no-nested-ternary: error
107 | no-new-object: error
108 | no-new-wrappers: error
109 | no-octal-escape: error
110 | no-proto: error
111 | no-prototype-builtins: 'off'
112 | no-restricted-globals: [error, window]
113 | no-restricted-properties:
114 | - error
115 | - object: imports
116 | property: format
117 | message: Use template strings
118 | - object: pkg
119 | property: initFormat
120 | message: Use template strings
121 | - object: Lang
122 | property: copyProperties
123 | message: Use Object.assign()
124 | - object: Lang
125 | property: bind
126 | message: Use arrow notation or Function.prototype.bind()
127 | - object: Lang
128 | property: Class
129 | message: Use ES6 classes
130 | no-restricted-syntax:
131 | - error
132 | - selector: >-
133 | MethodDefinition[key.name="_init"] >
134 | FunctionExpression[params.length=1] >
135 | BlockStatement[body.length=1]
136 | CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] >
137 | Identifier:first-child
138 | message: _init() that only calls super._init() is unnecessary
139 | - selector: >-
140 | MethodDefinition[key.name="_init"] >
141 | FunctionExpression[params.length=0] >
142 | BlockStatement[body.length=1]
143 | CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"]
144 | message: _init() that only calls super._init() is unnecessary
145 | - selector: BinaryExpression[operator="instanceof"][right.name="Array"]
146 | message: Use Array.isArray()
147 | no-return-assign: error
148 | no-return-await: error
149 | no-self-compare: error
150 | no-shadow: error
151 | no-shadow-restricted-names: error
152 | no-spaced-func: error
153 | no-tabs: error
154 | no-template-curly-in-string: error
155 | no-throw-literal: error
156 | no-trailing-spaces: error
157 | no-undef-init: error
158 | no-unneeded-ternary: error
159 | no-unused-expressions: error
160 | no-unused-vars:
161 | - error
162 | # Vars use a suffix _ instead of a prefix because of file-scope private vars
163 | - varsIgnorePattern: (^unused|_$)
164 | argsIgnorePattern: ^(unused|_)
165 | no-useless-call: error
166 | no-useless-computed-key: error
167 | no-useless-concat: error
168 | no-useless-constructor: error
169 | no-useless-rename: error
170 | no-useless-return: error
171 | no-whitespace-before-property: error
172 | no-with: error
173 | nonblock-statement-body-position:
174 | - error
175 | - below
176 | object-curly-newline:
177 | - error
178 | - consistent: true
179 | multiline: true
180 | object-curly-spacing: error
181 | object-shorthand: error
182 | operator-assignment: error
183 | operator-linebreak: error
184 | padded-blocks:
185 | - error
186 | - never
187 | # These may be a bit controversial, we can try them out and enable them later
188 | # prefer-const: error
189 | # prefer-destructuring: error
190 | prefer-numeric-literals: error
191 | prefer-promise-reject-errors: error
192 | prefer-rest-params: error
193 | prefer-spread: error
194 | prefer-template: error
195 | require-await: error
196 | rest-spread-spacing: error
197 | semi:
198 | - error
199 | - always
200 | semi-spacing:
201 | - error
202 | - before: false
203 | after: true
204 | semi-style: error
205 | space-before-blocks: error
206 | space-before-function-paren:
207 | - error
208 | - named: never
209 | # for `function ()` and `async () =>`, preserve space around keywords
210 | anonymous: always
211 | asyncArrow: always
212 | space-in-parens: error
213 | space-infix-ops:
214 | - error
215 | - int32Hint: false
216 | space-unary-ops: error
217 | spaced-comment: error
218 | switch-colon-spacing: error
219 | symbol-description: error
220 | template-curly-spacing: error
221 | template-tag-spacing: error
222 | unicode-bom: error
223 | wrap-iife:
224 | - error
225 | - inside
226 | yield-star-spacing: error
227 | yoda: error
228 | globals:
229 | _: readonly
230 | globalThis: readonly
231 | imports: readonly
232 | log: readonly
233 | logError: readonly
234 | pkg: readonly
235 | print: readonly
236 | printerr: readonly
237 | parserOptions:
238 | ecmaVersion: 2020
239 |
--------------------------------------------------------------------------------