[vscode] support managing string keys and manage typescript.tsdk (#112185) (#112210)

Co-authored-by: spalger <spalger@users.noreply.github.com>

Co-authored-by: Spencer <email@spalger.com>
Co-authored-by: spalger <spalger@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-09-15 02:58:06 -04:00 committed by GitHub
parent 36c02ae31c
commit fd17b5975e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 40 deletions

View file

@ -8,7 +8,7 @@
export interface ManagedConfigKey { export interface ManagedConfigKey {
key: string; key: string;
value: Record<string, any>; value: string | Record<string, any>;
} }
/** /**
@ -37,4 +37,9 @@ export const MANAGED_CONFIG_KEYS: ManagedConfigKey[] = [
['**/packages/kbn-pm/dist/index.js']: true, ['**/packages/kbn-pm/dist/index.js']: true,
}, },
}, },
{
key: 'typescript.tsdk',
// we use a relative path here so that it works with remote vscode connections
value: './node_modules/typescript/lib',
},
]; ];

View file

@ -22,6 +22,10 @@ const TEST_KEYS: ManagedConfigKey[] = [
world: [1, 2, 3], world: [1, 2, 3],
}, },
}, },
{
key: 'stringKey',
value: 'foo',
},
]; ];
const run = (json?: string) => updateVscodeConfig(TEST_KEYS, '', json); const run = (json?: string) => updateVscodeConfig(TEST_KEYS, '', json);
@ -35,7 +39,9 @@ it('updates the passed JSON with the managed settings', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -50,7 +56,9 @@ it('initialized empty or undefined json values', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -63,14 +71,16 @@ it('initialized empty or undefined json values', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
}); });
it('replaces conflicting managed keys which do not have object values', () => { it('replaces conflicting managed keys which do not have matching value types', () => {
expect(run(`{ "key": false }`)).toMatchInlineSnapshot(` expect(run(`{ "key": false, "stringKey": { "a": "B" } }`)).toMatchInlineSnapshot(`
// @managed // @managed
{ {
"key": { "key": {
@ -78,7 +88,9 @@ it('replaces conflicting managed keys which do not have object values', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -122,7 +134,9 @@ it('persists comments in the original file', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -148,7 +162,9 @@ it('overrides old values for managed keys', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -176,7 +192,9 @@ it('does not modify properties with leading `// self managed` comment', () => {
// self managed // self managed
"key": { "key": {
"world": [5] "world": [5]
} },
// self managed
"stringKey": "--"
} }
`); `);
@ -186,7 +204,9 @@ it('does not modify properties with leading `// self managed` comment', () => {
// self managed // self managed
"key": { "key": {
"world": [5] "world": [5]
} },
// self managed
"stringKey": "--"
} }
`); `);
@ -210,7 +230,9 @@ it('does not modify child properties with leading `// self managed` comment', ()
"world": [5], "world": [5],
// @managed // @managed
"hello": true "hello": true
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -236,7 +258,9 @@ it('does not modify unknown child properties', () => {
"world": [5], "world": [5],
// @managed // @managed
"hello": true "hello": true
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -262,7 +286,9 @@ it('removes managed properties which are no longer managed', () => {
"world": [5], "world": [5],
// @managed // @managed
"hello": true "hello": true
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -286,7 +312,9 @@ it('wipes out child keys which conflict with newly managed child keys', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -308,7 +336,9 @@ it('correctly formats info text when specified', () => {
"hello": true, "hello": true,
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// @managed
"stringKey": "foo"
} }
`); `);
@ -321,7 +351,10 @@ it('allows "// self managed" comments conflicting with "// @managed" comments to
// @managed // @managed
// self managed // self managed
"hello": ["world"] "hello": ["world"]
} },
// @managed
// self managed
"stringKey": 12345
} }
`); `);
@ -333,7 +366,9 @@ it('allows "// self managed" comments conflicting with "// @managed" comments to
"hello": ["world"], "hello": ["world"],
// @managed // @managed
"world": [1, 2, 3] "world": [1, 2, 3]
} },
// self managed
"stringKey": 12345
} }
`); `);

View file

@ -25,11 +25,20 @@ const isManaged = (node?: t.Node) =>
(c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === '@managed' (c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === '@managed'
); );
const isSelfManaged = (node?: t.Node) => const isSelfManaged = (node?: t.Node) => {
!!node?.leadingComments?.some( const result = !!node?.leadingComments?.some(
(c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === 'self managed' (c) => c.type === 'CommentLine' && c.value.trim().toLocaleLowerCase() === 'self managed'
); );
// if we find a node which is both managed and self managed remove the managed comment
if (result && node && isManaged(node)) {
node.leadingComments =
node.leadingComments?.filter((c) => c.value.trim() !== '@managed') ?? null;
}
return result;
};
const remove = <T>(arr: T[], value: T) => { const remove = <T>(arr: T[], value: T) => {
const index = arr.indexOf(value); const index = arr.indexOf(value);
if (index > -1) { if (index > -1) {
@ -37,16 +46,16 @@ const remove = <T>(arr: T[], value: T) => {
} }
}; };
const createManagedChildProp = (key: string, value: any) => { const createManagedProp = (key: string, value: any) => {
const childProp = t.objectProperty(t.stringLiteral(key), parseExpression(JSON.stringify(value))); const childProp = t.objectProperty(t.stringLiteral(key), parseExpression(JSON.stringify(value)));
t.addComment(childProp, 'leading', ' @managed', true); t.addComment(childProp, 'leading', ' @managed', true);
return childProp; return childProp;
}; };
const createManagedProp = (key: string, value: Record<string, any>) => { const createObjectPropOfManagedValues = (key: string, value: Record<string, any>) => {
return t.objectProperty( return t.objectProperty(
t.stringLiteral(key), t.stringLiteral(key),
t.objectExpression(Object.entries(value).map(([k, v]) => createManagedChildProp(k, v))) t.objectExpression(Object.entries(value).map(([k, v]) => createManagedProp(k, v)))
); );
}; };
@ -57,8 +66,16 @@ const createManagedProp = (key: string, value: Record<string, any>) => {
* @param key the key name to add * @param key the key name to add
* @param value managed value which should be set at `key` * @param value managed value which should be set at `key`
*/ */
const addManagedProp = (ast: t.ObjectExpression, key: string, value: Record<string, any>) => { const addManagedProp = (
ast.properties.push(createManagedProp(key, value)); ast: t.ObjectExpression,
key: string,
value: string | Record<string, any>
) => {
ast.properties.push(
typeof value === 'string'
? createManagedProp(key, value)
: createObjectPropOfManagedValues(key, value)
);
}; };
/** /**
@ -72,7 +89,7 @@ const addManagedProp = (ast: t.ObjectExpression, key: string, value: Record<stri
const replaceManagedProp = ( const replaceManagedProp = (
ast: t.ObjectExpression, ast: t.ObjectExpression,
existing: BasicObjectProp, existing: BasicObjectProp,
value: Record<string, any> value: string | Record<string, any>
) => { ) => {
remove(ast.properties, existing); remove(ast.properties, existing);
addManagedProp(ast, existing.key.value, value); addManagedProp(ast, existing.key.value, value);
@ -98,15 +115,11 @@ const mergeManagedProperties = (
if (!existing) { if (!existing) {
// add the new managed prop // add the new managed prop
properties.push(createManagedChildProp(key, value)); properties.push(createManagedProp(key, value));
continue; continue;
} }
if (isSelfManaged(existing)) { if (isSelfManaged(existing)) {
// strip "// @managed" comment if conflicting with "// self managed"
existing.leadingComments = (existing.leadingComments ?? []).filter(
(c) => c.value.trim() !== '@managed'
);
continue; continue;
} }
@ -119,7 +132,7 @@ const mergeManagedProperties = (
// take over the unmanaged child prop by deleting the previous prop and replacing it // take over the unmanaged child prop by deleting the previous prop and replacing it
// with a brand new one // with a brand new one
remove(properties, existing); remove(properties, existing);
properties.push(createManagedChildProp(key, value)); properties.push(createManagedProp(key, value));
} }
// iterate through the props to find "// @managed" props which are no longer in // iterate through the props to find "// @managed" props which are no longer in
@ -170,20 +183,29 @@ export function updateVscodeConfig(keys: ManagedConfigKey[], infoText: string, j
continue; continue;
} }
if (existingProp && existingProp.value.type === 'ObjectExpression') { if (typeof value === 'object') {
// setting exists and is an object so merge properties of `value` with it if (existingProp && existingProp.value.type === 'ObjectExpression') {
mergeManagedProperties(existingProp.value.properties, value); // setting exists and is an object so merge properties of `value` with it
mergeManagedProperties(existingProp.value.properties, value);
continue;
}
if (existingProp) {
// setting exists but its value is not an object expression so replace it
replaceManagedProp(ast, existingProp, value);
continue;
}
// setting isn't in config file so create it
addManagedProp(ast, key, value);
continue; continue;
} }
if (existingProp) { if (existingProp) {
// setting exists but its value is not an object expression so replace it
replaceManagedProp(ast, existingProp, value); replaceManagedProp(ast, existingProp, value);
continue; } else {
addManagedProp(ast, key, value);
} }
// setting isn't in config file so create it
addManagedProp(ast, key, value);
} }
ast.leadingComments = [ ast.leadingComments = [