[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 {
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,
},
},
{
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],
},
},
{
key: 'stringKey',
value: 'foo',
},
];
const run = (json?: string) => updateVscodeConfig(TEST_KEYS, '', json);
@ -35,7 +39,9 @@ it('updates the passed JSON with the managed settings', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -50,7 +56,9 @@ it('initialized empty or undefined json values', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -63,14 +71,16 @@ it('initialized empty or undefined json values', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
});
it('replaces conflicting managed keys which do not have object values', () => {
expect(run(`{ "key": false }`)).toMatchInlineSnapshot(`
it('replaces conflicting managed keys which do not have matching value types', () => {
expect(run(`{ "key": false, "stringKey": { "a": "B" } }`)).toMatchInlineSnapshot(`
// @managed
{
"key": {
@ -78,7 +88,9 @@ it('replaces conflicting managed keys which do not have object values', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -122,7 +134,9 @@ it('persists comments in the original file', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -148,7 +162,9 @@ it('overrides old values for managed keys', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -176,7 +192,9 @@ it('does not modify properties with leading `// self managed` comment', () => {
// self managed
"key": {
"world": [5]
}
},
// self managed
"stringKey": "--"
}
`);
@ -186,7 +204,9 @@ it('does not modify properties with leading `// self managed` comment', () => {
// self managed
"key": {
"world": [5]
}
},
// self managed
"stringKey": "--"
}
`);
@ -210,7 +230,9 @@ it('does not modify child properties with leading `// self managed` comment', ()
"world": [5],
// @managed
"hello": true
}
},
// @managed
"stringKey": "foo"
}
`);
@ -236,7 +258,9 @@ it('does not modify unknown child properties', () => {
"world": [5],
// @managed
"hello": true
}
},
// @managed
"stringKey": "foo"
}
`);
@ -262,7 +286,9 @@ it('removes managed properties which are no longer managed', () => {
"world": [5],
// @managed
"hello": true
}
},
// @managed
"stringKey": "foo"
}
`);
@ -286,7 +312,9 @@ it('wipes out child keys which conflict with newly managed child keys', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -308,7 +336,9 @@ it('correctly formats info text when specified', () => {
"hello": true,
// @managed
"world": [1, 2, 3]
}
},
// @managed
"stringKey": "foo"
}
`);
@ -321,7 +351,10 @@ it('allows "// self managed" comments conflicting with "// @managed" comments to
// @managed
// self managed
"hello": ["world"]
}
},
// @managed
// self managed
"stringKey": 12345
}
`);
@ -333,7 +366,9 @@ it('allows "// self managed" comments conflicting with "// @managed" comments to
"hello": ["world"],
// @managed
"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'
);
const isSelfManaged = (node?: t.Node) =>
!!node?.leadingComments?.some(
const isSelfManaged = (node?: t.Node) => {
const result = !!node?.leadingComments?.some(
(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 index = arr.indexOf(value);
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)));
t.addComment(childProp, 'leading', ' @managed', true);
return childProp;
};
const createManagedProp = (key: string, value: Record<string, any>) => {
const createObjectPropOfManagedValues = (key: string, value: Record<string, any>) => {
return t.objectProperty(
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 value managed value which should be set at `key`
*/
const addManagedProp = (ast: t.ObjectExpression, key: string, value: Record<string, any>) => {
ast.properties.push(createManagedProp(key, value));
const addManagedProp = (
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 = (
ast: t.ObjectExpression,
existing: BasicObjectProp,
value: Record<string, any>
value: string | Record<string, any>
) => {
remove(ast.properties, existing);
addManagedProp(ast, existing.key.value, value);
@ -98,15 +115,11 @@ const mergeManagedProperties = (
if (!existing) {
// add the new managed prop
properties.push(createManagedChildProp(key, value));
properties.push(createManagedProp(key, value));
continue;
}
if (isSelfManaged(existing)) {
// strip "// @managed" comment if conflicting with "// self managed"
existing.leadingComments = (existing.leadingComments ?? []).filter(
(c) => c.value.trim() !== '@managed'
);
continue;
}
@ -119,7 +132,7 @@ const mergeManagedProperties = (
// take over the unmanaged child prop by deleting the previous prop and replacing it
// with a brand new one
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
@ -170,20 +183,29 @@ export function updateVscodeConfig(keys: ManagedConfigKey[], infoText: string, j
continue;
}
if (existingProp && existingProp.value.type === 'ObjectExpression') {
// setting exists and is an object so merge properties of `value` with it
mergeManagedProperties(existingProp.value.properties, value);
if (typeof value === 'object') {
if (existingProp && existingProp.value.type === 'ObjectExpression') {
// 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;
}
if (existingProp) {
// setting exists but its value is not an object expression so replace it
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 = [