Summary: - Upgrades to build-related packages: - Upgrade typescript, related libraries and typings. - Upgrade webpack, eslint; add tsc-watch, node-dev, eslint_d. - Build organization changes: - Build webpack from original typescript, transpiling only; with errors still reported by a background tsc watching process. - Typescript-related changes: - Reduce imports of AWS dependencies (very noticeable speedup) - Avoid auto-loading global @types - Client code is now built with isolatedModules flag (for safe transpilation) - Use allowJs to avoid copying JS files manually. - Linting changes - Enhance Arcanist ESLintLinter to run before/after commands, and set up to use eslint_d - Update eslint config, and include .eslintignore to avoid linting generated files. - Include a bunch of eslint-prompted and eslint-generated fixes - Add no-unused-expression rule to eslint, and fix a few warnings about it - Other items: - Refactor cssInput to avoid circular dependency - Remove a bit of unused code, libraries, dependencies Test Plan: No behavior changes, all existing tests pass. There are 30 tests fewer reported because `test_gpath.py` was removed (it's been unused for years) Reviewers: paulfitz Reviewed By: paulfitz Subscribers: paulfitz Differential Revision: https://phab.getgrist.com/D3498pull/214/head
parent
64ff9ccd0a
commit
dd2eadc86e
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* koSession offers observables whose values are tied to the browser session or history:
|
||||
*
|
||||
* sessionValue(key) - an observable preserved across history entries and reloads.
|
||||
*
|
||||
* Note: we could also support "browserValue", shared across all tabs and across browser restarts
|
||||
* (same as sessionValue but using window.localStorage), but it seems more appropriate to store
|
||||
* such values on the server.
|
||||
*/
|
||||
|
||||
/* global window, $ */
|
||||
|
||||
var _ = require('underscore');
|
||||
var ko = require('knockout');
|
||||
|
||||
/**
|
||||
* Maps a string key to an observable. The space of keys is shared for all kinds of observables,
|
||||
* and they differ only in where they store their state. Each observable gets several extra
|
||||
* properties:
|
||||
* @property {String} ksKey The key used for storage. It should be unique across koSession values.
|
||||
* @property {Object} ksDefault The default value if the storage doesn't have one.
|
||||
* @property {Function} ksFetch The method to fetch the value from storage.
|
||||
* @property {Function} ksSave The method to save the value to storage.
|
||||
*/
|
||||
var _sessionValues = {};
|
||||
|
||||
function createObservable(key, defaultValue, methods) {
|
||||
var obs = _sessionValues[key];
|
||||
if (!obs) {
|
||||
_sessionValues[key] = obs = ko.observable();
|
||||
obs.ksKey = key;
|
||||
obs.ksDefaultValue = defaultValue;
|
||||
obs.ksFetch = methods.fetch;
|
||||
obs.ksSave = methods.save;
|
||||
obs.dispose = methods.dispose;
|
||||
|
||||
// We initialize the observable before setting rateLimit, to ensure that the initialization
|
||||
// doesn't end up triggering subscribers that are about to be added (which seems to be a bit
|
||||
// of a problem with rateLimit extender, and possibly deferred). This workaround relies on the
|
||||
// fact that the extender modifies its target without creating a new one.
|
||||
obs(obs.ksFetch());
|
||||
obs.extend({deferred: true});
|
||||
|
||||
obs.subscribe(function(newValue) {
|
||||
if (newValue !== this.ksFetch()) {
|
||||
console.log("koSession: %s changed %s -> %s", this.ksKey, this.ksFetch(), newValue);
|
||||
this.ksSave(newValue);
|
||||
}
|
||||
}, obs);
|
||||
}
|
||||
return obs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable whose value sticks across reloads and navigation, but is different for
|
||||
* different browser tabs. E.g. it may be used to reflect whether a side pane is open.
|
||||
* The `key` isn't visible to the user, so pick any unique string name.
|
||||
*/
|
||||
function sessionValue(key, optDefault) {
|
||||
return createObservable(key, optDefault, sessionValueMethods);
|
||||
}
|
||||
exports.sessionValue = sessionValue;
|
||||
|
||||
var sessionValueMethods = {
|
||||
'fetch': function() {
|
||||
var value = window.sessionStorage.getItem(this.ksKey);
|
||||
if (!value) {
|
||||
return this.ksDefaultValue;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return this.ksDefaultValue;
|
||||
}
|
||||
},
|
||||
'save': function(value) {
|
||||
window.sessionStorage.setItem(this.ksKey, JSON.stringify(value));
|
||||
},
|
||||
'dispose': function(value) {
|
||||
window.sessionStorage.removeItem(this.ksKey);
|
||||
}
|
||||
};
|
||||
|
||||
function onApplyState() {
|
||||
_.each(_sessionValues, function(obs, key) {
|
||||
obs(obs.ksFetch());
|
||||
});
|
||||
}
|
||||
|
||||
$(window).on('applyState', onApplyState);
|
@ -0,0 +1,12 @@
|
||||
import {colors, vars} from 'app/client/ui2018/cssVars';
|
||||
import {styled} from 'grainjs';
|
||||
|
||||
export const cssInput = styled('input', `
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
font-size: ${vars.mediumFontSize};
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
border: 1px solid ${colors.darkGrey};
|
||||
outline: none;
|
||||
`);
|
@ -1,5 +1,6 @@
|
||||
export interface INotifier {
|
||||
deleteUser(userId: number): Promise<void>;
|
||||
// for test purposes, check if any notifications are in progress
|
||||
readonly testPending: boolean;
|
||||
|
||||
deleteUser(userId: number): Promise<void>;
|
||||
}
|
||||
|
@ -1,144 +0,0 @@
|
||||
from six.moves import xrange
|
||||
|
||||
|
||||
def _is_array(obj):
|
||||
return isinstance(obj, list)
|
||||
|
||||
def get(obj, path):
|
||||
"""
|
||||
Looks up and returns a path in the object. Returns None if the path isn't there.
|
||||
"""
|
||||
for part in path:
|
||||
try:
|
||||
obj = obj[part]
|
||||
except(KeyError, IndexError):
|
||||
return None
|
||||
return obj
|
||||
|
||||
def glob(obj, path, func, extra_arg):
|
||||
"""
|
||||
Resolves wildcards in `path`, calling func for all matching paths. Returns the number of
|
||||
times that func was called.
|
||||
obj - An object to scan.
|
||||
path - Path to an item in an object or an array in obj. May contain the special key '*', which
|
||||
-- for arrays only -- means "for all indices".
|
||||
func - Will be called as func(subobj, key, fullPath, extraArg).
|
||||
extra_arg - An arbitrary value to pass along to func, for convenience.
|
||||
Returns count of matching paths, for which func got called.
|
||||
"""
|
||||
return _globHelper(obj, path, path, func, extra_arg)
|
||||
|
||||
def _globHelper(obj, path, full_path, func, extra_arg):
|
||||
for i, part in enumerate(path[:-1]):
|
||||
if part == "*" and _is_array(obj):
|
||||
# We got an array wildcard
|
||||
subpath = path[i + 1:]
|
||||
count = 0
|
||||
for subobj in obj:
|
||||
count += _globHelper(subobj, subpath, full_path, func, extra_arg)
|
||||
return count
|
||||
|
||||
try:
|
||||
obj = obj[part]
|
||||
except:
|
||||
raise Exception("gpath.glob: non-existent object at " +
|
||||
describe(full_path[:len(full_path) - len(path) + i + 1]))
|
||||
|
||||
return func(obj, path[-1], full_path, extra_arg) or 1
|
||||
|
||||
def place(obj, path, value):
|
||||
"""
|
||||
Sets or deletes an object property in DocObj.
|
||||
gpath - Path to an Object in obj.
|
||||
value - Any value. Setting None will remove the selected object key.
|
||||
"""
|
||||
return glob(obj, path, _placeHelper, value)
|
||||
|
||||
def _placeHelper(subobj, key, full_path, value):
|
||||
if not isinstance(subobj, dict):
|
||||
raise Exception("gpath.place: not a plain object at " + describe(dirname(full_path)))
|
||||
|
||||
if value is not None:
|
||||
subobj[key] = value
|
||||
elif key in subobj:
|
||||
del subobj[key]
|
||||
|
||||
def _checkIsArray(subobj, errPrefix, index, itemPath, isInsert):
|
||||
"""
|
||||
This is a helper for checking operations on arrays, and throwing descriptive errors.
|
||||
"""
|
||||
if subobj is None:
|
||||
raise Exception(errPrefix + ": non-existent object at " + describe(dirname(itemPath)))
|
||||
elif not _is_array(subobj):
|
||||
raise Exception(errPrefix + ": not an array at " + describe(dirname(itemPath)))
|
||||
else:
|
||||
length = len(subobj)
|
||||
validIndex = (isinstance(index, int) and index >= 0 and index < length)
|
||||
validInsertIndex = (index is None or index == length)
|
||||
if not (validIndex or (isInsert and validInsertIndex)):
|
||||
raise Exception(errPrefix + ": invalid array index: " + describe(itemPath))
|
||||
|
||||
def insert(obj, path, value):
|
||||
"""
|
||||
Inserts an element into an array in DocObj.
|
||||
gpath - Path to an item in an array in obj.
|
||||
The new value will be inserted before the item pointed to by gpath.
|
||||
The last component of gpath may be null, in which case the value is appended at the end.
|
||||
value - Any value.
|
||||
"""
|
||||
return glob(obj, path, _insertHelper, value)
|
||||
|
||||
def _insertHelper(subobj, index, fullPath, value):
|
||||
_checkIsArray(subobj, "gpath.insert", index, fullPath, True)
|
||||
if index is None:
|
||||
subobj.append(value)
|
||||
else:
|
||||
subobj.insert(index, value)
|
||||
|
||||
def update(obj, path, value):
|
||||
"""
|
||||
Updates an element in an array in DocObj.
|
||||
gpath - Path to an item in an array in obj.
|
||||
value - Any value.
|
||||
"""
|
||||
return glob(obj, path, _updateHelper, value)
|
||||
|
||||
def _updateHelper(subobj, index, fullPath, value):
|
||||
if index == '*':
|
||||
_checkIsArray(subobj, "gpath.update", None, fullPath, True)
|
||||
for i in xrange(len(subobj)):
|
||||
subobj[i] = value
|
||||
return len(subobj)
|
||||
else:
|
||||
_checkIsArray(subobj, "gpath.update", index, fullPath, False)
|
||||
subobj[index] = value
|
||||
|
||||
def remove(obj, path):
|
||||
"""
|
||||
Removes an element from an array in DocObj.
|
||||
gpath - Path to an item in an array in obj.
|
||||
"""
|
||||
return glob(obj, path, _removeHelper, None)
|
||||
|
||||
def _removeHelper(subobj, index, fullPath, _):
|
||||
_checkIsArray(subobj, "gpath.remove", index, fullPath, False)
|
||||
del subobj[index]
|
||||
|
||||
|
||||
def dirname(path):
|
||||
"""
|
||||
Returns path without the last component, like a directory name in a filesystem path.
|
||||
"""
|
||||
return path[:-1]
|
||||
|
||||
def basename(path):
|
||||
"""
|
||||
Returns the last component of path, like base name of a filesystem path.
|
||||
"""
|
||||
return path[-1] if path else None
|
||||
|
||||
def describe(path):
|
||||
"""
|
||||
Returns a human-readable representation of path.
|
||||
"""
|
||||
return "/" + "/".join(str(p) for p in path)
|
@ -1,159 +0,0 @@
|
||||
import unittest
|
||||
import gpath
|
||||
|
||||
class TestGpath(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.obj = {
|
||||
"foo": [{"bar": 1}, {"bar": 2}, {"baz": 3}],
|
||||
"hello": "world"
|
||||
}
|
||||
|
||||
def test_get(self):
|
||||
self.assertEqual(gpath.get(self.obj, ["foo", 0, "bar"]), 1)
|
||||
self.assertEqual(gpath.get(self.obj, ["foo", 2]), {"baz": 3})
|
||||
self.assertEqual(gpath.get(self.obj, ["hello"]), "world")
|
||||
self.assertEqual(gpath.get(self.obj, []), self.obj)
|
||||
|
||||
self.assertEqual(gpath.get(self.obj, ["foo", 0, "baz"]), None)
|
||||
self.assertEqual(gpath.get(self.obj, ["foo", 4]), None)
|
||||
self.assertEqual(gpath.get(self.obj, ["foo", 4, "baz"]), None)
|
||||
self.assertEqual(gpath.get(self.obj, [0]), None)
|
||||
|
||||
def test_set(self):
|
||||
gpath.place(self.obj, ["foo"], {"bar": 1, "baz": 2})
|
||||
self.assertEqual(self.obj["foo"], {"bar": 1, "baz": 2})
|
||||
gpath.place(self.obj, ["foo", "bar"], 17)
|
||||
self.assertEqual(self.obj["foo"], {"bar": 17, "baz": 2})
|
||||
gpath.place(self.obj, ["foo", "baz"], None)
|
||||
self.assertEqual(self.obj["foo"], {"bar": 17})
|
||||
|
||||
self.assertEqual(self.obj["hello"], "world")
|
||||
gpath.place(self.obj, ["hello"], None)
|
||||
self.assertFalse("hello" in self.obj)
|
||||
gpath.place(self.obj, ["hello"], None) # OK to remove a non-existent property.
|
||||
self.assertFalse("hello" in self.obj)
|
||||
gpath.place(self.obj, ["hello"], "blah")
|
||||
self.assertEqual(self.obj["hello"], "blah")
|
||||
|
||||
def test_set_strict(self):
|
||||
with self.assertRaisesRegex(Exception, r"non-existent"):
|
||||
gpath.place(self.obj, ["bar", 4], 17)
|
||||
|
||||
with self.assertRaisesRegex(Exception, r"not a plain object"):
|
||||
gpath.place(self.obj, ["foo", 0], 17)
|
||||
|
||||
|
||||
def test_insert(self):
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 1}, {"bar": 2}, {"baz": 3}])
|
||||
gpath.insert(self.obj, ["foo", 0], "asdf")
|
||||
self.assertEqual(self.obj["foo"], ["asdf", {"bar": 1}, {"bar": 2}, {"baz": 3}])
|
||||
gpath.insert(self.obj, ["foo", 3], "hello")
|
||||
self.assertEqual(self.obj["foo"], ["asdf", {"bar": 1}, {"bar": 2}, "hello", {"baz": 3}])
|
||||
gpath.insert(self.obj, ["foo", None], "world")
|
||||
self.assertEqual(self.obj["foo"],
|
||||
["asdf", {"bar": 1}, {"bar": 2}, "hello", {"baz": 3}, "world"])
|
||||
|
||||
def test_insert_strict(self):
|
||||
with self.assertRaisesRegex(Exception, r'not an array'):
|
||||
gpath.insert(self.obj, ["foo"], "asdf")
|
||||
|
||||
with self.assertRaisesRegex(Exception, r'invalid.*index'):
|
||||
gpath.insert(self.obj, ["foo", -1], 17)
|
||||
|
||||
with self.assertRaisesRegex(Exception, r'invalid.*index'):
|
||||
gpath.insert(self.obj, ["foo", "foo"], 17)
|
||||
|
||||
def test_update(self):
|
||||
"""update should update array items"""
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 1}, {"bar": 2}, {"baz": 3}])
|
||||
gpath.update(self.obj, ["foo", 0], "asdf")
|
||||
self.assertEqual(self.obj["foo"], ["asdf", {"bar": 2}, {"baz": 3}])
|
||||
gpath.update(self.obj, ["foo", 2], "hello")
|
||||
self.assertEqual(self.obj["foo"], ["asdf", {"bar": 2}, "hello"])
|
||||
gpath.update(self.obj, ["foo", 1], None)
|
||||
self.assertEqual(self.obj["foo"], ["asdf", None, "hello"])
|
||||
|
||||
def test_update_strict(self):
|
||||
"""update should be strict"""
|
||||
with self.assertRaisesRegex(Exception, r'non-existent'):
|
||||
gpath.update(self.obj, ["bar", 4], 17)
|
||||
with self.assertRaisesRegex(Exception, r'not an array'):
|
||||
gpath.update(self.obj, ["foo"], 17)
|
||||
with self.assertRaisesRegex(Exception, r'invalid.*index'):
|
||||
gpath.update(self.obj, ["foo", -1], 17)
|
||||
with self.assertRaisesRegex(Exception, r'invalid.*index'):
|
||||
gpath.update(self.obj, ["foo", None], 17)
|
||||
|
||||
def test_remove(self):
|
||||
"""remove should remove indices"""
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 1}, {"bar": 2}, {"baz": 3}])
|
||||
gpath.remove(self.obj, ["foo", 0])
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 2}, {"baz": 3}])
|
||||
gpath.remove(self.obj, ["foo", 1])
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 2}])
|
||||
gpath.remove(self.obj, ["foo", 0])
|
||||
self.assertEqual(self.obj["foo"], [])
|
||||
|
||||
def test_remove_strict(self):
|
||||
"""remove should be strict"""
|
||||
with self.assertRaisesRegex(Exception, r'non-existent'):
|
||||
gpath.remove(self.obj, ["bar", 4])
|
||||
with self.assertRaisesRegex(Exception, r'not an array'):
|
||||
gpath.remove(self.obj, ["foo"])
|
||||
with self.assertRaisesRegex(Exception, r'invalid.*index'):
|
||||
gpath.remove(self.obj, ["foo", -1])
|
||||
with self.assertRaisesRegex(Exception, r'invalid.*index'):
|
||||
gpath.remove(self.obj, ["foo", None])
|
||||
|
||||
def test_glob(self):
|
||||
"""glob should scan arrays"""
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 1}, {"bar": 2}, {"baz": 3}])
|
||||
|
||||
self.assertEqual(gpath.place(self.obj, ["foo", "*", "bar"], 17), 3)
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 17}, {"bar": 17}, {"baz": 3, "bar": 17}])
|
||||
|
||||
with self.assertRaisesRegex(Exception, r'non-existent object at \/foo\/\*\/bad'):
|
||||
gpath.place(self.obj, ["foo", "*", "bad", "test"], 10)
|
||||
|
||||
self.assertEqual(gpath.update(self.obj, ["foo", "*"], "hello"), 3)
|
||||
self.assertEqual(self.obj["foo"], ["hello", "hello", "hello"])
|
||||
|
||||
def test_glob_strict_wildcard(self):
|
||||
"""should only support tail wildcard for updates"""
|
||||
with self.assertRaisesRegex(Exception, r'invalid array index'):
|
||||
gpath.remove(self.obj, ["foo", "*"])
|
||||
with self.assertRaisesRegex(Exception, r'invalid array index'):
|
||||
gpath.insert(self.obj, ["foo", "*"], 1)
|
||||
|
||||
def test_glob_wildcard_keys(self):
|
||||
"""should not scan object keys"""
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 1}, {"bar": 2}, {"baz": 3}])
|
||||
|
||||
self.assertEqual(gpath.place(self.obj, ["foo", 0, "*"], 17), 1)
|
||||
self.assertEqual(self.obj["foo"], [{"bar": 1, '*': 17}, {"bar": 2}, {"baz": 3}])
|
||||
|
||||
with self.assertRaisesRegex(Exception, r'non-existent'):
|
||||
gpath.place(self.obj, ["*", 0, "bar"], 17)
|
||||
|
||||
def test_glob_nested(self):
|
||||
"""should scan nested arrays"""
|
||||
self.obj = [{"a": [1,2,3]}, {"a": [4,5,6]}, {"a": [7,8,9]}]
|
||||
self.assertEqual(gpath.update(self.obj, ["*", "a", "*"], 5), 9)
|
||||
self.assertEqual(self.obj, [{"a": [5,5,5]}, {"a": [5,5,5]}, {"a": [5,5,5]}])
|
||||
|
||||
def test_dirname(self):
|
||||
"""dirname should return path without last component"""
|
||||
self.assertEqual(gpath.dirname(["foo", "bar", "baz"]), ["foo", "bar"])
|
||||
self.assertEqual(gpath.dirname([1, 2]), [1])
|
||||
self.assertEqual(gpath.dirname(["foo"]), [])
|
||||
self.assertEqual(gpath.dirname([]), [])
|
||||
|
||||
def test_basename(self):
|
||||
"""basename should return the last component of path"""
|
||||
self.assertEqual(gpath.basename(["foo", "bar", "baz"]), "baz")
|
||||
self.assertEqual(gpath.basename([1, 2]), 2)
|
||||
self.assertEqual(gpath.basename(["foo"]), "foo")
|
||||
self.assertEqual(gpath.basename([]), None)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Loading…
Reference in new issue