diff --git a/build/.gitignore b/build/.gitignore index 679674617c7..61cf49cb9a1 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1,2 +1 @@ *.js.map -lib/policies/policyDto.* diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml index d1d431505f6..bc8e962f91b 100644 --- a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -118,7 +118,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - script: node build/lib/policies/policyGenerator build/lib/policies/policyData.jsonc darwin + - script: node build/lib/policies darwin displayName: Generate policy definitions retryCountOnTaskFailure: 3 diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml index bdc807fdae5..072d64ec2d9 100644 --- a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -120,7 +120,7 @@ steps: - template: ../../common/install-builtin-extensions.yml@self - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - powershell: node build\lib\policies\policyGenerator build\lib\policies\policyData.jsonc win32 + - powershell: node build\lib\policies win32 displayName: Generate Group Policy definitions retryCountOnTaskFailure: 3 diff --git a/build/lib/policies/policyGenerator.js b/build/lib/policies.js similarity index 70% rename from build/lib/policies/policyGenerator.js rename to build/lib/policies.js index 7ddb6956c18..d2ef760870d 100644 --- a/build/lib/policies/policyGenerator.js +++ b/build/lib/policies.js @@ -1,37 +1,4 @@ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -40,12 +7,24 @@ Object.defineProperty(exports, "__esModule", { value: true }); * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const minimist_1 = __importDefault(require("minimist")); +const child_process_1 = require("child_process"); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); -const JSONC = __importStar(require("jsonc-parser")); -const product = require('../../../product.json'); -const packageJson = require('../../../package.json'); +const byline_1 = __importDefault(require("byline")); +const ripgrep_1 = require("@vscode/ripgrep"); +const tree_sitter_1 = __importDefault(require("tree-sitter")); +const { typescript } = require('tree-sitter-typescript'); +const product = require('../../product.json'); +const packageJson = require('../../package.json'); +function isNlsString(value) { + return value ? typeof value !== 'string' : false; +} +function isStringArray(value) { + return !value.some(s => isNlsString(s)); +} +function isNlsStringArray(value) { + return value.every(s => isNlsString(s)); +} var PolicyType; (function (PolicyType) { PolicyType["Boolean"] = "boolean"; @@ -128,12 +107,12 @@ ${this.renderProfileManifestValue(translations)} } } class BooleanPolicy extends BasePolicy { - static from(category, policy) { - const { name, minimumVersion, localization, type } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new BooleanPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); @@ -164,17 +143,23 @@ class BooleanPolicy extends BasePolicy { boolean`; } } +class ParseError extends Error { + constructor(message, moduleName, node) { + super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); + } +} class NumberPolicy extends BasePolicy { defaultValue; - static from(category, policy) { - const { type, default: defaultValue, name, minimumVersion, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'number') { return undefined; } - if (typeof defaultValue !== 'number') { - throw new Error(`Missing required 'default' property.`); + const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); + if (typeof defaultValue === 'undefined') { + throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); } - return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); } constructor(name, category, minimumVersion, description, moduleName, defaultValue) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -206,12 +191,12 @@ class NumberPolicy extends BasePolicy { } } class StringPolicy extends BasePolicy { - static from(category, policy) { - const { type, name, minimumVersion, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new StringPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.String, name, category, minimumVersion, description, moduleName); @@ -239,12 +224,12 @@ class StringPolicy extends BasePolicy { } } class ObjectPolicy extends BasePolicy { - static from(category, policy) { - const { type, name, minimumVersion, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new ObjectPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Object, name, category, minimumVersion, description, moduleName); @@ -275,20 +260,26 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { enum_; enumDescriptions; - static from(category, policy) { - const { type, name, minimumVersion, enum: enumValue, localization } = policy; + static from(name, category, minimumVersion, description, moduleName, settingNode) { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - const enum_ = enumValue; + const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); if (!enum_) { return undefined; } - if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { - throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + if (!isStringArray(enum_)) { + throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); } - const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); - return new StringEnumPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', enum_, enumDescriptions); + const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); + if (!enumDescriptions) { + throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); + } + else if (!isNlsStringArray(enumDescriptions)) { + throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); + } + return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); } constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); @@ -331,6 +322,178 @@ class StringEnumPolicy extends BasePolicy { `; } } +const NumberQ = { + Q: `(number) @value`, + value(matches) { + const match = matches[0]; + if (!match) { + return undefined; + } + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + return parseInt(value); + } +}; +const StringQ = { + Q: `[ + (string (string_fragment) @value) + (call_expression + function: [ + (identifier) @localizeFn (#eq? @localizeFn localize) + (member_expression + object: (identifier) @nlsObj (#eq? @nlsObj nls) + property: (property_identifier) @localizeFn (#eq? @localizeFn localize) + ) + ] + arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) + ) + ]`, + value(matches) { + const match = matches[0]; + if (!match) { + return undefined; + } + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; + if (nlsKey) { + return { value, nlsKey }; + } + else { + return value; + } + } +}; +const StringArrayQ = { + Q: `(array ${StringQ.Q})`, + value(matches) { + if (matches.length === 0) { + return undefined; + } + return matches.map(match => { + return StringQ.value([match]); + }); + } +}; +function getProperty(qtype, moduleName, node, key) { + const query = new tree_sitter_1.default.Query(typescript, `( + (pair + key: [(property_identifier)(string)] @key + value: ${qtype.Q} + ) + (#any-of? @key "${key}" "'${key}'") + )`); + try { + const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); + return qtype.value(matches); + } + catch (e) { + throw new ParseError(e.message, moduleName, node); + } +} +function getNumberProperty(moduleName, node, key) { + return getProperty(NumberQ, moduleName, node, key); +} +function getStringProperty(moduleName, node, key) { + return getProperty(StringQ, moduleName, node, key); +} +function getStringArrayProperty(moduleName, node, key) { + return getProperty(StringArrayQ, moduleName, node, key); +} +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; +function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { + const name = getStringProperty(moduleName, policyNode, 'name'); + if (!name) { + throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); + } + else if (isNlsString(name)) { + throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); + } + const categoryName = getStringProperty(moduleName, configurationNode, 'title'); + if (!categoryName) { + throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); + } + else if (!isNlsString(categoryName)) { + throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); + } + const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; + let category = categories.get(categoryKey); + if (!category) { + category = { moduleName, name: categoryName }; + categories.set(categoryKey, category); + } + const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); + if (!minimumVersion) { + throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); + } + else if (isNlsString(minimumVersion)) { + throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); + } + const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); + if (!description) { + throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); + } + if (!isNlsString(description)) { + throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); + } + let result; + for (const policyType of PolicyTypes) { + if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { + break; + } + } + if (!result) { + throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); + } + return result; +} +function getPolicies(moduleName, node) { + const query = new tree_sitter_1.default.Query(typescript, ` + ( + (call_expression + function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) + arguments: (arguments (object (pair + key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") + value: (object (pair + key: [(property_identifier)(string)(computed_property_name)] + value: (object (pair + key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") + value: (object) @policy + )) @setting + )) + )) @configuration) + ) + ) + `); + const categories = new Map(); + return query.matches(node).map(m => { + const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; + const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; + const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; + return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); + }); +} +async function getFiles(root) { + return new Promise((c, e) => { + const result = []; + const rg = (0, child_process_1.spawn)(ripgrep_1.rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); + const stream = (0, byline_1.default)(rg.stdout.setEncoding('utf8')); + stream.on('data', path => result.push(path)); + stream.on('error', err => e(err)); + stream.on('end', () => c(result)); + }); +} function renderADMX(regKey, versions, categories, policies) { versions = versions.map(v => v.replace(/\./g, '_')); return ` @@ -594,15 +757,7 @@ async function getSpecificNLS(resourceUrlTemplate, languageId, version) { throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); } const { contents: result } = await res.json(); - // TODO: support module namespacing - // Flatten all moduleName keys to empty string - const flattened = { '': {} }; - for (const moduleName in result) { - for (const nlsKey in result[moduleName]) { - flattened[''][nlsKey] = result[moduleName][nlsKey]; - } - } - return flattened; + return result; } function parseVersion(version) { const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); @@ -646,45 +801,18 @@ async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageI } return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; -async function parsePolicies(policyDataFile) { - const contents = JSONC.parse(await fs_1.promises.readFile(policyDataFile, { encoding: 'utf8' })); - const categories = new Map(); - for (const category of contents.categories) { - categories.set(category.key, category); - } +async function parsePolicies() { + const parser = new tree_sitter_1.default(); + parser.setLanguage(typescript); + const files = await getFiles(process.cwd()); + const base = path_1.default.join(process.cwd(), 'src'); const policies = []; - for (const policy of contents.policies) { - const category = categories.get(policy.category); - if (!category) { - throw new Error(`Unknown category: ${policy.category}`); - } - let result; - for (const policyType of PolicyTypes) { - if (result = policyType.from(category, policy)) { - break; - } - } - if (!result) { - throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); - } - policies.push(result); + for (const file of files) { + const moduleName = path_1.default.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); + const contents = await fs_1.promises.readFile(file, { encoding: 'utf8' }); + const tree = parser.parse(contents); + policies.push(...getPolicies(moduleName, tree.rootNode)); } - // Sort policies first by category name, then by policy name - policies.sort((a, b) => { - const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); - if (categoryCompare !== 0) { - return categoryCompare; - } - return a.name.localeCompare(b.name); - }); return policies; } async function getTranslations() { @@ -732,14 +860,8 @@ async function darwinMain(policies, translations) { } } async function main() { - const args = (0, minimist_1.default)(process.argv.slice(2)); - if (args._.length !== 2) { - console.error(`Usage: node build/lib/policies `); - process.exit(1); - } - const policyDataFile = args._[0]; - const platform = args._[1]; - const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); + const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); + const platform = process.argv[2]; if (platform === 'darwin') { await darwinMain(policies, translations); } @@ -747,14 +869,19 @@ async function main() { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - console.error(err); + if (err instanceof ParseError) { + console.error(`Parse Error:`, err.message); + } + else { + console.error(err); + } process.exit(1); }); } -//# sourceMappingURL=policyGenerator.js.map \ No newline at end of file +//# sourceMappingURL=policies.js.map \ No newline at end of file diff --git a/build/lib/policies/policyGenerator.ts b/build/lib/policies.ts similarity index 69% rename from build/lib/policies/policyGenerator.ts rename to build/lib/policies.ts index 5fb06942f67..381d2f4c1ac 100644 --- a/build/lib/policies/policyGenerator.ts +++ b/build/lib/policies.ts @@ -3,17 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import minimist from 'minimist'; +import { spawn } from 'child_process'; import { promises as fs } from 'fs'; import path from 'path'; -import { CategoryDto, ExportedPolicyDataDto, PolicyDto } from './policyDto'; -import * as JSONC from 'jsonc-parser'; - -const product = require('../../../product.json'); -const packageJson = require('../../../package.json'); +import byline from 'byline'; +import { rgPath } from '@vscode/ripgrep'; +import Parser from 'tree-sitter'; +const { typescript } = require('tree-sitter-typescript'); +const product = require('../../product.json'); +const packageJson = require('../../package.json'); type NlsString = { value: string; nlsKey: string }; +function isNlsString(value: string | NlsString | undefined): value is NlsString { + return value ? typeof value !== 'string' : false; +} + +function isStringArray(value: (string | NlsString)[]): value is string[] { + return !value.some(s => isNlsString(s)); +} + +function isNlsStringArray(value: (string | NlsString)[]): value is NlsString[] { + return value.every(s => isNlsString(s)); +} + interface Category { readonly moduleName: string; readonly name: NlsString; @@ -133,14 +146,21 @@ ${this.renderProfileManifestValue(translations)} class BooleanPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { - const { name, minimumVersion, localization, type } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): BooleanPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'boolean') { return undefined; } - return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new BooleanPolicy(name, category, minimumVersion, description, moduleName); } private constructor( @@ -183,20 +203,35 @@ class BooleanPolicy extends BasePolicy { } } +class ParseError extends Error { + constructor(message: string, moduleName: string, node: Parser.SyntaxNode) { + super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); + } +} + class NumberPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { - const { type, default: defaultValue, name, minimumVersion, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): NumberPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'number') { return undefined; } - if (typeof defaultValue !== 'number') { - throw new Error(`Missing required 'default' property.`); + const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); + + if (typeof defaultValue === 'undefined') { + throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); } - return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); } private constructor( @@ -241,14 +276,21 @@ class NumberPolicy extends BasePolicy { class StringPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { - const { type, name, minimumVersion, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): StringPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new StringPolicy(name, category, minimumVersion, description, moduleName); } private constructor( @@ -289,14 +331,21 @@ class StringPolicy extends BasePolicy { class ObjectPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { - const { type, name, minimumVersion, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): ObjectPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'object' && type !== 'array') { return undefined; } - return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + return new ObjectPolicy(name, category, minimumVersion, description, moduleName); } private constructor( @@ -338,32 +387,39 @@ class ObjectPolicy extends BasePolicy { class StringEnumPolicy extends BasePolicy { - static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { - const { type, name, minimumVersion, enum: enumValue, localization } = policy; + static from( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + settingNode: Parser.SyntaxNode + ): StringEnumPolicy | undefined { + const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } - const enum_ = enumValue; + const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); if (!enum_) { return undefined; } - if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { - throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + if (!isStringArray(enum_)) { + throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); } - const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); - return new StringEnumPolicy( - name, - { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, - minimumVersion, - { nlsKey: localization.description.key, value: localization.description.value }, - '', - enum_, - enumDescriptions - ); + + const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); + + if (!enumDescriptions) { + throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); + } else if (!isNlsStringArray(enumDescriptions)) { + throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); + } + + return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); } private constructor( @@ -419,6 +475,226 @@ class StringEnumPolicy extends BasePolicy { } } +interface QType { + Q: string; + value(matches: Parser.QueryMatch[]): T | undefined; +} + +const NumberQ: QType = { + Q: `(number) @value`, + + value(matches: Parser.QueryMatch[]): number | undefined { + const match = matches[0]; + + if (!match) { + return undefined; + } + + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + + return parseInt(value); + } +}; + +const StringQ: QType = { + Q: `[ + (string (string_fragment) @value) + (call_expression + function: [ + (identifier) @localizeFn (#eq? @localizeFn localize) + (member_expression + object: (identifier) @nlsObj (#eq? @nlsObj nls) + property: (property_identifier) @localizeFn (#eq? @localizeFn localize) + ) + ] + arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) + ) + ]`, + + value(matches: Parser.QueryMatch[]): string | NlsString | undefined { + const match = matches[0]; + + if (!match) { + return undefined; + } + + const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; + + if (!value) { + throw new Error(`Missing required 'value' property.`); + } + + const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; + + if (nlsKey) { + return { value, nlsKey }; + } else { + return value; + } + } +}; + +const StringArrayQ: QType<(string | NlsString)[]> = { + Q: `(array ${StringQ.Q})`, + + value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { + if (matches.length === 0) { + return undefined; + } + + return matches.map(match => { + return StringQ.value([match]) as string | NlsString; + }); + } +}; + +function getProperty(qtype: QType, moduleName: string, node: Parser.SyntaxNode, key: string): T | undefined { + const query = new Parser.Query( + typescript, + `( + (pair + key: [(property_identifier)(string)] @key + value: ${qtype.Q} + ) + (#any-of? @key "${key}" "'${key}'") + )` + ); + + try { + const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); + return qtype.value(matches); + } catch (e) { + throw new ParseError(e.message, moduleName, node); + } +} + +function getNumberProperty(moduleName: string, node: Parser.SyntaxNode, key: string): number | undefined { + return getProperty(NumberQ, moduleName, node, key); +} + +function getStringProperty(moduleName: string, node: Parser.SyntaxNode, key: string): string | NlsString | undefined { + return getProperty(StringQ, moduleName, node, key); +} + +function getStringArrayProperty(moduleName: string, node: Parser.SyntaxNode, key: string): (string | NlsString)[] | undefined { + return getProperty(StringArrayQ, moduleName, node, key); +} + +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; + +function getPolicy( + moduleName: string, + configurationNode: Parser.SyntaxNode, + settingNode: Parser.SyntaxNode, + policyNode: Parser.SyntaxNode, + categories: Map +): Policy { + const name = getStringProperty(moduleName, policyNode, 'name'); + + if (!name) { + throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); + } else if (isNlsString(name)) { + throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); + } + + const categoryName = getStringProperty(moduleName, configurationNode, 'title'); + + if (!categoryName) { + throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); + } else if (!isNlsString(categoryName)) { + throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); + } + + const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; + let category = categories.get(categoryKey); + + if (!category) { + category = { moduleName, name: categoryName }; + categories.set(categoryKey, category); + } + + const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); + + if (!minimumVersion) { + throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); + } else if (isNlsString(minimumVersion)) { + throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); + } + + const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); + + if (!description) { + throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); + } if (!isNlsString(description)) { + throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); + } + + let result: Policy | undefined; + + for (const policyType of PolicyTypes) { + if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { + break; + } + } + + if (!result) { + throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); + } + + return result; +} + +function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { + const query = new Parser.Query(typescript, ` + ( + (call_expression + function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) + arguments: (arguments (object (pair + key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") + value: (object (pair + key: [(property_identifier)(string)(computed_property_name)] + value: (object (pair + key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") + value: (object) @policy + )) @setting + )) + )) @configuration) + ) + ) + `); + + const categories = new Map(); + + return query.matches(node).map(m => { + const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; + const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; + const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; + return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); + }); +} + +async function getFiles(root: string): Promise { + return new Promise((c, e) => { + const result: string[] = []; + const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); + const stream = byline(rg.stdout.setEncoding('utf8')); + stream.on('data', path => result.push(path)); + stream.on('error', err => e(err)); + stream.on('end', () => c(result)); + }); +} + function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { versions = versions.map(v => v.replace(/\./g, '_')); @@ -693,7 +969,7 @@ type Translations = { languageId: string; languageTranslations: LanguageTranslat type Version = [number, number, number]; -async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise { +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, @@ -709,17 +985,7 @@ async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, v } const { contents: result } = await res.json() as { contents: LanguageTranslations }; - - // TODO: support module namespacing - // Flatten all moduleName keys to empty string - const flattened: LanguageTranslations = { '': {} }; - for (const moduleName in result) { - for (const nlsKey in result[moduleName]) { - flattened[''][nlsKey] = result[moduleName][nlsKey]; - } - } - - return flattened; + return result; } function parseVersion(version: string): Version { @@ -768,52 +1034,21 @@ async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: s return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; +async function parsePolicies(): Promise { + const parser = new Parser(); + parser.setLanguage(typescript); -async function parsePolicies(policyDataFile: string): Promise { - const contents = JSONC.parse(await fs.readFile(policyDataFile, { encoding: 'utf8' })) as ExportedPolicyDataDto; - const categories = new Map(); - for (const category of contents.categories) { - categories.set(category.key, category); + const files = await getFiles(process.cwd()); + const base = path.join(process.cwd(), 'src'); + const policies = []; + + for (const file of files) { + const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); + const contents = await fs.readFile(file, { encoding: 'utf8' }); + const tree = parser.parse(contents); + policies.push(...getPolicies(moduleName, tree.rootNode)); } - const policies: Policy[] = []; - for (const policy of contents.policies) { - const category = categories.get(policy.category); - if (!category) { - throw new Error(`Unknown category: ${policy.category}`); - } - - let result: Policy | undefined; - for (const policyType of PolicyTypes) { - if (result = policyType.from(category, policy)) { - break; - } - } - - if (!result) { - throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); - } - - policies.push(result); - } - - // Sort policies first by category name, then by policy name - policies.sort((a, b) => { - const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); - if (categoryCompare !== 0) { - return categoryCompare; - } - return a.name.localeCompare(b.name); - }); - return policies; } @@ -877,29 +1112,26 @@ async function darwinMain(policies: Policy[], translations: Translations) { } async function main() { - const args = minimist(process.argv.slice(2)); - if (args._.length !== 2) { - console.error(`Usage: node build/lib/policies `); - process.exit(1); - } - - const policyDataFile = args._[0]; - const platform = args._[1]; - const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); + const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); + const platform = process.argv[2]; if (platform === 'darwin') { await darwinMain(policies, translations); } else if (platform === 'win32') { await windowsMain(policies, translations); } else { - console.error(`Usage: node build/lib/policies `); + console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { - console.error(err); + if (err instanceof ParseError) { + console.error(`Parse Error:`, err.message); + } else { + console.error(err); + } process.exit(1); }); } diff --git a/build/lib/policies/copyPolicyDto.js b/build/lib/policies/copyPolicyDto.js deleted file mode 100644 index 9a7d518ced8..00000000000 --- a/build/lib/policies/copyPolicyDto.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const fs = __importStar(require("fs")); -const path = __importStar(require("path")); -const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); -const destFile = path.join(__dirname, 'policyDto.ts'); -try { - // Check if source file exists - if (!fs.existsSync(sourceFile)) { - console.error(`Error: Source file not found: ${sourceFile}`); - console.error('Please ensure policyDto.ts exists in src/vs/base/common/'); - process.exit(1); - } - // Copy the file - fs.copyFileSync(sourceFile, destFile); -} -catch (error) { - console.error(`Error copying policyDto.ts: ${error.message}`); - process.exit(1); -} -//# sourceMappingURL=copyPolicyDto.js.map \ No newline at end of file diff --git a/build/lib/policies/copyPolicyDto.ts b/build/lib/policies/copyPolicyDto.ts deleted file mode 100644 index a0ec9fc4c1d..00000000000 --- a/build/lib/policies/copyPolicyDto.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import * as path from 'path'; - -const sourceFile = path.join(__dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); -const destFile = path.join(__dirname, 'policyDto.ts'); - -try { - // Check if source file exists - if (!fs.existsSync(sourceFile)) { - console.error(`Error: Source file not found: ${sourceFile}`); - console.error('Please ensure policyDto.ts exists in src/vs/base/common/'); - process.exit(1); - } - - // Copy the file - fs.copyFileSync(sourceFile, destFile); -} catch (error) { - console.error(`Error copying policyDto.ts: ${(error as Error).message}`); - process.exit(1); -} diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc deleted file mode 100644 index 096b911e5d3..00000000000 --- a/build/lib/policies/policyData.jsonc +++ /dev/null @@ -1,277 +0,0 @@ -/** THIS FILE IS AUTOMATICALLY GENERATED USING `code --export-policy-data`. DO NOT MODIFY IT MANUALLY. **/ -{ - "categories": [ - { - "key": "Extensions", - "name": { - "key": "extensionsConfigurationTitle", - "value": "Extensions" - } - }, - { - "key": "IntegratedTerminal", - "name": { - "key": "terminalIntegratedConfigurationTitle", - "value": "Integrated Terminal" - } - }, - { - "key": "InteractiveSession", - "name": { - "key": "interactiveSessionConfigurationTitle", - "value": "Chat" - } - }, - { - "key": "Telemetry", - "name": { - "key": "telemetryConfigurationTitle", - "value": "Telemetry" - } - }, - { - "key": "Update", - "name": { - "key": "updateConfigurationTitle", - "value": "Update" - } - } - ], - "policies": [ - { - "key": "chat.mcp.gallery.serviceUrl", - "name": "McpGalleryServiceUrl", - "category": "InteractiveSession", - "minimumVersion": "1.101", - "localization": { - "description": { - "key": "mcp.gallery.serviceUrl", - "value": "Configure the MCP Gallery service URL to connect to" - } - }, - "type": "string", - "default": "" - }, - { - "key": "extensions.gallery.serviceUrl", - "name": "ExtensionGalleryServiceUrl", - "category": "Extensions", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "extensions.gallery.serviceUrl", - "value": "Configure the Marketplace service URL to connect to" - } - }, - "type": "string", - "default": "" - }, - { - "key": "extensions.allowed", - "name": "AllowedExtensions", - "category": "Extensions", - "minimumVersion": "1.96", - "localization": { - "description": { - "key": "extensions.allowed.policy", - "value": "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions" - } - }, - "type": "object", - "default": "*" - }, - { - "key": "chat.tools.global.autoApprove", - "name": "ChatToolsAutoApprove", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "autoApprove2.description", - "value": "Global auto approve also known as \"YOLO mode\" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine." - } - }, - "type": "boolean", - "default": false - }, - { - "key": "chat.mcp.access", - "name": "ChatMCP", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.mcp.access", - "value": "Controls access to installed Model Context Protocol servers." - }, - "enumDescriptions": [ - { - "key": "chat.mcp.access.none", - "value": "No access to MCP servers." - }, - { - "key": "chat.mcp.access.registry", - "value": "Allows access to MCP servers installed from the registry that VS Code is connected to." - }, - { - "key": "chat.mcp.access.any", - "value": "Allow access to any installed MCP server." - } - ] - }, - "type": "string", - "default": "all", - "enum": [ - "none", - "registry", - "all" - ] - }, - { - "key": "chat.extensionTools.enabled", - "name": "ChatAgentExtensionTools", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.extensionToolsEnabled", - "value": "Enable using tools contributed by third-party extensions." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "chat.agent.enabled", - "name": "ChatAgentMode", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.agent.enabled.description", - "value": "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "chat.promptFiles", - "name": "ChatPromptFiles", - "category": "InteractiveSession", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "chat.promptFiles.policy", - "value": "Enables reusable prompt and instruction files in Chat sessions." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "chat.tools.terminal.enableAutoApprove", - "name": "ChatToolsTerminalEnableAutoApprove", - "category": "IntegratedTerminal", - "minimumVersion": "1.104", - "localization": { - "description": { - "key": "autoApproveMode.description", - "value": "Controls whether to allow auto approval in the run in terminal tool." - } - }, - "type": "boolean", - "default": true - }, - { - "key": "update.mode", - "name": "UpdateMode", - "category": "Update", - "minimumVersion": "1.67", - "localization": { - "description": { - "key": "updateMode", - "value": "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service." - }, - "enumDescriptions": [ - { - "key": "none", - "value": "Disable updates." - }, - { - "key": "manual", - "value": "Disable automatic background update checks. Updates will be available if you manually check for updates." - }, - { - "key": "start", - "value": "Check for updates only on startup. Disable automatic background update checks." - }, - { - "key": "default", - "value": "Enable automatic update checks. Code will check for updates automatically and periodically." - } - ] - }, - "type": "string", - "default": "default", - "enum": [ - "none", - "manual", - "start", - "default" - ] - }, - { - "key": "telemetry.telemetryLevel", - "name": "TelemetryLevel", - "category": "Telemetry", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "telemetry.telemetryLevel.policyDescription", - "value": "Controls the level of telemetry." - }, - "enumDescriptions": [ - { - "key": "telemetry.telemetryLevel.default", - "value": "Sends usage data, errors, and crash reports." - }, - { - "key": "telemetry.telemetryLevel.error", - "value": "Sends general error telemetry and crash reports." - }, - { - "key": "telemetry.telemetryLevel.crash", - "value": "Sends OS level crash reports." - }, - { - "key": "telemetry.telemetryLevel.off", - "value": "Disables all product telemetry." - } - ] - }, - "type": "string", - "default": "all", - "enum": [ - "all", - "error", - "crash", - "off" - ] - }, - { - "key": "telemetry.feedback.enabled", - "name": "EnableFeedback", - "category": "Telemetry", - "minimumVersion": "1.99", - "localization": { - "description": { - "key": "telemetry.feedback.enabled", - "value": "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options." - } - }, - "type": "boolean", - "default": true - } - ] -} diff --git a/build/package.json b/build/package.json index ef715fbfb6a..fbbf5264776 100644 --- a/build/package.json +++ b/build/package.json @@ -64,12 +64,9 @@ }, "type": "commonjs", "scripts": { - "copy-policy-dto": "node lib/policies/copyPolicyDto.js", - "prebuild-ts": "npm run copy-policy-dto", - "build-ts": "cd .. && npx tsgo --project build/tsconfig.build.json", - "compile": "npm run build-ts", - "watch": "npm run build-ts -- --watch", - "npmCheckJs": "npm run build-ts -- --noEmit" + "compile": "cd .. && npx tsgo --project build/tsconfig.build.json", + "watch": "cd .. && npx tsgo --project build/tsconfig.build.json --watch", + "npmCheckJs": "cd .. && npx tsgo --project build/tsconfig.build.json --noEmit" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", diff --git a/src/vs/base/common/policy.ts b/src/vs/base/common/policy.ts index 7fa484a477e..1e97392d5e2 100644 --- a/src/vs/base/common/policy.ts +++ b/src/vs/base/common/policy.ts @@ -3,52 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../nls.js'; import { IDefaultAccount } from './defaultAccount.js'; export type PolicyName = string; -export type LocalizedValue = { - key: string; - value: string; -}; - -export enum PolicyCategory { - Extensions = 'Extensions', - IntegratedTerminal = 'IntegratedTerminal', - InteractiveSession = 'InteractiveSession', - Telemetry = 'Telemetry', - Update = 'Update', -} - -export const PolicyCategoryData: { - [key in PolicyCategory]: { name: LocalizedValue } -} = { - [PolicyCategory.Extensions]: { - name: { - key: 'extensionsConfigurationTitle', value: localize('extensionsConfigurationTitle', "Extensions"), - } - }, - [PolicyCategory.IntegratedTerminal]: { - name: { - key: 'terminalIntegratedConfigurationTitle', value: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), - } - }, - [PolicyCategory.InteractiveSession]: { - name: { - key: 'interactiveSessionConfigurationTitle', value: localize('interactiveSessionConfigurationTitle', "Chat"), - } - }, - [PolicyCategory.Telemetry]: { - name: { - key: 'telemetryConfigurationTitle', value: localize('telemetryConfigurationTitle', "Telemetry"), - } - }, - [PolicyCategory.Update]: { - name: { - key: 'updateConfigurationTitle', value: localize('updateConfigurationTitle', "Update"), - } - } -}; export interface IPolicy { @@ -57,27 +14,15 @@ export interface IPolicy { */ readonly name: PolicyName; - /** - * The policy category. - */ - readonly category: PolicyCategory; - /** * The Code version in which this policy was introduced. */ readonly minimumVersion: `${number}.${number}`; /** - * Localization info for the policy. - * - * IMPORTANT: the key values for these must be unique to avoid collisions, as during the export time the module information is not available. + * The policy description (optional). */ - readonly localization: { - /** The localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's description property. */ - description: LocalizedValue; - /** List of localization key or key value pair. If only a key is provided, the default value will fallback to the parent configuration's enumDescriptions property. */ - enumDescriptions?: LocalizedValue[]; - }; + readonly description?: string; /** * The value that an ACCOUNT-based feature will use when its corresponding policy is active. diff --git a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts index 043f137b202..4d089e71eb8 100644 --- a/src/vs/platform/configuration/test/common/configurationRegistry.test.ts +++ b/src/vs/platform/configuration/test/common/configurationRegistry.test.ts @@ -7,7 +7,6 @@ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../common/configurationRegistry.js'; import { Registry } from '../../../registry/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationRegistry', () => { @@ -90,18 +89,14 @@ suite('ConfigurationRegistry', () => { 'type': 'object', policy: { name: 'policy', - category: PolicyCategory.Extensions, - minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } + minimumVersion: '1.0.0' } }, 'policy2': { 'type': 'object', policy: { name: 'policy', - category: PolicyCategory.Extensions, - minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } + minimumVersion: '1.0.0' } } } diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index a258c16a082..4e765d63efe 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -20,7 +20,6 @@ import { NullLogService } from '../../../log/common/log.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { NullPolicyService } from '../../../policy/common/policy.js'; import { Registry } from '../../../registry/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; suite('ConfigurationService.test.ts', () => { @@ -375,9 +374,7 @@ suite('ConfigurationService.test.ts', () => { 'default': 'isSet', policy: { name: 'configurationService.policySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } } } diff --git a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts index 5cc463e4ac0..ac30e8b5050 100644 --- a/src/vs/platform/configuration/test/common/policyConfiguration.test.ts +++ b/src/vs/platform/configuration/test/common/policyConfiguration.test.ts @@ -19,7 +19,6 @@ import { IPolicyService } from '../../../policy/common/policy.js'; import { FilePolicyService } from '../../../policy/common/filePolicyService.js'; import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; suite('PolicyConfiguration', () => { @@ -40,9 +39,7 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.settingB': { @@ -50,9 +47,7 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.objectSetting': { @@ -60,9 +55,7 @@ suite('PolicyConfiguration', () => { 'default': {}, policy: { name: 'PolicyObjectSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.arraySetting': { @@ -70,9 +63,7 @@ suite('PolicyConfiguration', () => { 'default': [], policy: { name: 'PolicyArraySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.booleanSetting': { @@ -80,9 +71,7 @@ suite('PolicyConfiguration', () => { 'default': true, policy: { name: 'PolicyBooleanSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'policy.internalSetting': { @@ -91,9 +80,7 @@ suite('PolicyConfiguration', () => { included: false, policy: { name: 'PolicyInternalSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, } } }, 'nonPolicy.setting': { @@ -280,9 +267,7 @@ suite('PolicyConfiguration', () => { 'default': 'defaultValueC', policy: { name: 'PolicySettingC', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' }, }, } }; Registry.as(Extensions.Configuration).registerConfiguration(deepClone(policyConfigurationNode)); diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index a10f4c9b3bb..e7b08b8f887 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -105,7 +105,6 @@ export interface NativeParsedArgs { 'skip-welcome'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; - 'export-policy-data'?: string; 'install-source'?: string; 'add-mcp'?: string[]; 'disable-updates'?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4a14c835bf9..c5f10d53040 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -145,7 +145,6 @@ export interface INativeEnvironmentService extends IEnvironmentService { useInMemorySecretStorage?: boolean; crossOriginIsolated?: boolean; - exportPolicyData?: string; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 535132f43a4..6b6c82c6d5d 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -254,10 +254,6 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron get editSessionId(): string | undefined { return this.args['editSessionId']; } - get exportPolicyData(): string | undefined { - return this.args['export-policy-data']; - } - get continueOn(): string | undefined { return this.args['continueOn']; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index d0ab1d6b4e5..9b0cf59752b 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -162,7 +162,6 @@ export const OPTIONS: OptionDescriptions> = { 'inspect-sharedprocess': { type: 'string', allowEmptyValue: true }, 'inspect-brk-sharedprocess': { type: 'string', allowEmptyValue: true }, 'export-default-configuration': { type: 'string' }, - 'export-policy-data': { type: 'string', allowEmptyValue: true }, 'install-source': { type: 'string' }, 'enable-smoke-test-driver': { type: 'boolean' }, 'logExtensionHostCommunication': { type: 'boolean' }, diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 35fe4290937..cd5c1945c5a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -9,7 +9,6 @@ import { Event } from '../../../base/common/event.js'; import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { IPager } from '../../../base/common/paging.js'; import { Platform } from '../../../base/common/platform.js'; -import { PolicyCategory } from '../../../base/common/policy.js'; import { URI } from '../../../base/common/uri.js'; import { localize, localize2 } from '../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; @@ -735,14 +734,8 @@ Registry.as(Extensions.Configuration) scope: ConfigurationScope.APPLICATION, policy: { name: 'AllowedExtensions', - category: PolicyCategory.Extensions, minimumVersion: '1.96', - localization: { - description: { - key: 'extensions.allowed.policy', - value: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), - } - } + description: localize('extensions.allowed.policy', "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions"), }, additionalProperties: false, patternProperties: { diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index e26d733e98e..ef676bdc69b 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -6,7 +6,6 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { mixin } from '../../../base/common/objects.js'; import { isWeb } from '../../../base/common/platform.js'; -import { PolicyCategory } from '../../../base/common/policy.js'; import { escapeRegExpCharacters } from '../../../base/common/strings.js'; import { localize } from '../../../nls.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; @@ -224,32 +223,8 @@ configurationRegistry.registerConfiguration({ 'tags': ['usesOnlineServices', 'telemetry'], 'policy': { name: 'TelemetryLevel', - category: PolicyCategory.Telemetry, minimumVersion: '1.99', - localization: { - description: { - key: 'telemetry.telemetryLevel.policyDescription', - value: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), - }, - enumDescriptions: [ - { - key: 'telemetry.telemetryLevel.default', - value: localize('telemetry.telemetryLevel.default', "Sends usage data, errors, and crash reports."), - }, - { - key: 'telemetry.telemetryLevel.error', - value: localize('telemetry.telemetryLevel.error', "Sends general error telemetry and crash reports."), - }, - { - key: 'telemetry.telemetryLevel.crash', - value: localize('telemetry.telemetryLevel.crash', "Sends OS level crash reports."), - }, - { - key: 'telemetry.telemetryLevel.off', - value: localize('telemetry.telemetryLevel.off', "Disables all product telemetry."), - } - ] - } + description: localize('telemetry.telemetryLevel.policyDescription', "Controls the level of telemetry."), } }, 'telemetry.feedback.enabled': { @@ -258,9 +233,7 @@ configurationRegistry.registerConfiguration({ description: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options."), policy: { name: 'EnableFeedback', - category: PolicyCategory.Telemetry, minimumVersion: '1.99', - localization: { description: { key: 'telemetry.feedback.enabled', value: localize('telemetry.feedback.enabled', "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.") } }, } }, // Deprecated telemetry setting diff --git a/src/vs/platform/update/common/update.config.contribution.ts b/src/vs/platform/update/common/update.config.contribution.ts index e5fb1abc0b6..d96926b5578 100644 --- a/src/vs/platform/update/common/update.config.contribution.ts +++ b/src/vs/platform/update/common/update.config.contribution.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { isWeb, isWindows } from '../../../base/common/platform.js'; -import { PolicyCategory } from '../../../base/common/policy.js'; import { localize } from '../../../nls.js'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js'; import { Registry } from '../../registry/common/platform.js'; @@ -31,29 +30,7 @@ configurationRegistry.registerConfiguration({ ], policy: { name: 'UpdateMode', - category: PolicyCategory.Update, minimumVersion: '1.67', - localization: { - description: { key: 'updateMode', value: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), }, - enumDescriptions: [ - { - key: 'none', - value: localize('none', "Disable updates."), - }, - { - key: 'manual', - value: localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), - }, - { - key: 'start', - value: localize('start', "Check for updates only on startup. Disable automatic background update checks."), - }, - { - key: 'default', - value: localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically."), - } - ] - }, } }, 'update.channel': { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index fc1a01be613..c79d3b3fb10 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -123,7 +123,6 @@ import { SAVE_TO_PROMPT_ACTION_ID, SAVE_TO_PROMPT_SLASH_COMMAND_NAME } from './p import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js'; import { ChatSessionsView } from './chatSessions/view/chatSessionsView.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -241,21 +240,18 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.GlobalAutoApprove]: { default: false, + // HACK: Description duplicated for policy parser. See https://github.com/microsoft/vscode/issues/254526 + description: nls.localize('autoApprove2.description', + 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' + ), markdownDescription: globalAutoApproveDescription.value, type: 'boolean', scope: ConfigurationScope.APPLICATION_MACHINE, tags: ['experimental'], policy: { name: 'ChatToolsAutoApprove', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_preview_features_enabled === false ? false : undefined, - localization: { - description: { - key: 'autoApprove2.description', - value: nls.localize('autoApprove2.description', 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.') - } - }, } }, [ChatConfiguration.AutoApproveEdits]: { @@ -348,7 +344,6 @@ configurationRegistry.registerConfiguration({ default: McpAccessValue.All, policy: { name: 'ChatMCP', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => { if (account.mcp === false) { @@ -359,23 +354,6 @@ configurationRegistry.registerConfiguration({ } return undefined; }, - localization: { - description: { - key: 'chat.mcp.access', - value: nls.localize('chat.mcp.access', "Controls access to installed Model Context Protocol servers.") - }, - enumDescriptions: [ - { - key: 'chat.mcp.access.none', value: nls.localize('chat.mcp.access.none', "No access to MCP servers."), - }, - { - key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), - }, - { - key: 'chat.mcp.access.any', value: nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") - } - ] - }, } }, [mcpAutoStartConfig]: { @@ -441,14 +419,8 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentExtensionTools', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - localization: { - description: { - key: 'chat.extensionToolsEnabled', - value: nls.localize('chat.extensionToolsEnabled', "Enable using tools contributed by third-party extensions.") - } - }, + description: nls.localize('chat.extensionToolsPolicy', "Enable using tools contributed by third-party extensions."), } }, [ChatConfiguration.AgentEnabled]: { @@ -457,15 +429,8 @@ configurationRegistry.registerConfiguration({ default: true, policy: { name: 'ChatAgentMode', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', value: (account) => account.chat_agent_enabled === false ? false : undefined, - localization: { - description: { - key: 'chat.agent.enabled.description', - value: nls.localize('chat.agent.enabled.description', "Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view."), - } - } } }, [ChatConfiguration.EnableMath]: { @@ -507,15 +472,8 @@ configurationRegistry.registerConfiguration({ included: false, policy: { name: 'McpGalleryServiceUrl', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.101', - value: (account) => account.mcpRegistryUrl, - localization: { - description: { - key: 'mcp.gallery.serviceUrl', - value: nls.localize('mcp.gallery.serviceUrl', "Configure the MCP Gallery service URL to connect to"), - } - } + value: (account) => account.mcpRegistryUrl }, }, [PromptsConfig.KEY]: { @@ -537,14 +495,8 @@ configurationRegistry.registerConfiguration({ tags: ['experimental', 'prompts', 'reusable prompts', 'prompt snippets', 'instructions'], policy: { name: 'ChatPromptFiles', - category: PolicyCategory.InteractiveSession, minimumVersion: '1.99', - localization: { - description: { - key: 'chat.promptFiles.policy', - value: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") - } - } + description: nls.localize('chat.promptFiles.policy', "Enables reusable prompt and instruction files in Chat sessions.") } }, [PromptsConfig.INSTRUCTIONS_LOCATION_KEY]: { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 012c4d398e5..b78c1385ac9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,7 +13,6 @@ import { mnemonicButtonLabel } from '../../../../base/common/labels.js'; import { Disposable, DisposableStore, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isNative, isWeb } from '../../../../base/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { MultiCommand } from '../../../../editor/browser/editorExtensions.js'; import { CopyAction, CutAction, PasteAction } from '../../../../editor/contrib/clipboard/browser/clipboard.js'; @@ -287,14 +286,7 @@ Registry.as(ConfigurationExtensions.Configuration) included: false, policy: { name: 'ExtensionGalleryServiceUrl', - category: PolicyCategory.Extensions, minimumVersion: '1.99', - localization: { - description: { - key: 'extensions.gallery.serviceUrl', - value: localize('extensions.gallery.serviceUrl', "Configure the Marketplace service URL to connect to"), - } - } }, }, 'extensions.supportNodeGlobalNavigator': { diff --git a/src/vs/workbench/contrib/policyExport/common/policyDto.ts b/src/vs/workbench/contrib/policyExport/common/policyDto.ts deleted file mode 100644 index 0408c74fc6e..00000000000 --- a/src/vs/workbench/contrib/policyExport/common/policyDto.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export type LocalizedValueDto = { - key: string; - value: string; -}; - -export interface CategoryDto { - key: string; - name: LocalizedValueDto; -} - -export interface PolicyDto { - key: string; - name: string; - category: string; - minimumVersion: `${number}.${number}`; - localization: { - description: LocalizedValueDto; - enumDescriptions?: LocalizedValueDto[]; - }; - type?: string | string[]; - default?: unknown; - enum?: string[]; -} - -export interface ExportedPolicyDataDto { - categories: CategoryDto[]; - policies: PolicyDto[]; -} diff --git a/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts b/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts deleted file mode 100644 index 51a0f8b42e1..00000000000 --- a/src/vs/workbench/contrib/policyExport/electron-browser/policyExport.contribution.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { IWorkbenchConfigurationService } from '../../../services/configuration/common/configuration.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { INativeEnvironmentService } from '../../../../platform/environment/common/environment.js'; -import { ILogService } from '../../../../platform/log/common/log.js'; -import { INativeHostService } from '../../../../platform/native/common/native.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; -import { URI } from '../../../../base/common/uri.js'; -import { VSBuffer } from '../../../../base/common/buffer.js'; -import { PolicyCategory, PolicyCategoryData } from '../../../../base/common/policy.js'; -import { ExportedPolicyDataDto } from '../common/policyDto.js'; -import { join } from '../../../../base/common/path.js'; - -export class PolicyExportContribution extends Disposable implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.policyExport'; - static readonly DEFAULT_POLICY_EXPORT_PATH = 'build/lib/policies/policyData.jsonc'; - - constructor( - @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, - @IExtensionService private readonly extensionService: IExtensionService, - @IFileService private readonly fileService: IFileService, - @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, - @INativeHostService private readonly nativeHostService: INativeHostService, - @IProgressService private readonly progressService: IProgressService, - @ILogService private readonly logService: ILogService, - ) { - super(); - - // Skip for non-development flows - if (this.nativeEnvironmentService.isBuilt) { - return; - } - - const policyDataPath = this.nativeEnvironmentService.exportPolicyData; - if (policyDataPath !== undefined) { - const defaultPath = join(this.nativeEnvironmentService.appRoot, PolicyExportContribution.DEFAULT_POLICY_EXPORT_PATH); - void this.exportPolicyDataAndQuit(policyDataPath ? policyDataPath : defaultPath); - } - } - - private log(msg: string | undefined, ...args: unknown[]) { - this.logService.info(`[${PolicyExportContribution.ID}]`, msg, ...args); - } - - private async exportPolicyDataAndQuit(policyDataPath: string): Promise { - try { - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: `Exporting policy data to ${policyDataPath}` - }, async (_progress) => { - this.log('Export started. Waiting for configurations to load.'); - await this.extensionService.whenInstalledExtensionsRegistered(); - await this.configurationService.whenRemoteConfigurationLoaded(); - - this.log('Extensions and configuration loaded.'); - const configurationRegistry = Registry.as(Extensions.Configuration); - const configurationProperties = { - ...configurationRegistry.getExcludedConfigurationProperties(), - ...configurationRegistry.getConfigurationProperties(), - }; - - const policyData: ExportedPolicyDataDto = { - categories: Object.values(PolicyCategory).map(category => ({ - key: category, - name: PolicyCategoryData[category].name - })), - policies: [] - }; - - for (const [key, schema] of Object.entries(configurationProperties)) { - // Check for the localization property for now to remain backwards compatible. - if (schema.policy?.localization) { - policyData.policies.push({ - key, - name: schema.policy.name, - category: schema.policy.category, - minimumVersion: schema.policy.minimumVersion, - localization: { - description: schema.policy.localization.description, - enumDescriptions: schema.policy.localization.enumDescriptions, - }, - type: schema.type, - default: schema.default, - enum: schema.enum, - }); - } - } - this.log(`Discovered ${policyData.policies.length} policies to export.`); - - const disclaimerComment = `/** THIS FILE IS AUTOMATICALLY GENERATED USING \`code --export-policy-data\`. DO NOT MODIFY IT MANUALLY. **/`; - const policyDataFileContent = `${disclaimerComment}\n${JSON.stringify(policyData, null, 4)}\n`; - await this.fileService.writeFile(URI.file(policyDataPath), VSBuffer.fromString(policyDataFileContent)); - this.log(`Successfully exported ${policyData.policies.length} policies to ${policyDataPath}.`); - }); - - await this.nativeHostService.exit(0); - } catch (error) { - this.log('Failed to export policy', error); - await this.nativeHostService.exit(1); - } - } -} - -registerWorkbenchContribution2( - PolicyExportContribution.ID, - PolicyExportContribution, - WorkbenchPhase.Eventually, -); diff --git a/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts b/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts deleted file mode 100644 index 725455533c0..00000000000 --- a/src/vs/workbench/contrib/policyExport/test/node/policyExport.integrationTest.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as cp from 'child_process'; -import { promises as fs } from 'fs'; -import * as os from 'os'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { isWindows } from '../../../../../base/common/platform.js'; -import { join } from '../../../../../base/common/path.js'; -import { FileAccess } from '../../../../../base/common/network.js'; -import * as util from 'util'; - -const exec = util.promisify(cp.exec); - -suite('PolicyExport Integration Tests', () => { - ensureNoDisposablesAreLeakedInTestSuite(); - - test('exported policy data matches checked-in file', async function () { - // This test launches VS Code with --export-policy-data flag, so it takes longer - this.timeout(60000); - - const rootPath = FileAccess.asFileUri('').fsPath.replace(/[\/\\]out[\/\\].*$/, ''); - const checkedInFile = join(rootPath, 'build/lib/policies/policyData.jsonc'); - const tempFile = join(os.tmpdir(), `policyData-test-${Date.now()}.jsonc`); - - try { - // Launch VS Code with --export-policy-data flag - const scriptPath = isWindows - ? join(rootPath, 'scripts', 'code.bat') - : join(rootPath, 'scripts', 'code.sh'); - - await exec(`"${scriptPath}" --export-policy-data="${tempFile}"`, { - cwd: rootPath - }); - - // Read both files - const [exportedContent, checkedInContent] = await Promise.all([ - fs.readFile(tempFile, 'utf-8'), - fs.readFile(checkedInFile, 'utf-8') - ]); - - // Compare contents - assert.strictEqual( - exportedContent, - checkedInContent, - 'Exported policy data should match the checked-in file. If this fails, run: ./scripts/code.sh --export-policy-data' - ); - } finally { - // Clean up temp file - try { - await fs.unlink(tempFile); - } catch { - // Ignore cleanup errors - } - } - }); -}); diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 76f7b87f762..ebd5bd55d8d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -7,7 +7,6 @@ import { Codicon } from '../../../../base/common/codicons.js'; import type { IStringDictionary } from '../../../../base/common/collections.js'; import { IJSONSchemaSnippet } from '../../../../base/common/jsonSchema.js'; import { isMacintosh, isWindows } from '../../../../base/common/platform.js'; -import { PolicyCategory } from '../../../../base/common/policy.js'; import { localize } from '../../../../nls.js'; import { ConfigurationScope, Extensions, IConfigurationRegistry, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import product from '../../../../platform/product/common/product.js'; @@ -646,14 +645,7 @@ export async function registerTerminalConfiguration(getFontSnippets: () => Promi default: true, policy: { name: 'ChatToolsTerminalEnableAutoApprove', - category: PolicyCategory.IntegratedTerminal, minimumVersion: '1.104', - localization: { - description: { - key: 'autoApproveMode.description', - value: localize('autoApproveMode.description', "Controls whether to allow auto approval in the run in terminal tool."), - } - } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts index 6cb93a3632f..fd6e51c680e 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditing.test.ts @@ -47,7 +47,6 @@ import { UserDataProfileService } from '../../../userDataProfile/common/userData import { IUserDataProfileService } from '../../../userDataProfile/common/userDataProfile.js'; import { IBrowserWorkbenchEnvironmentService } from '../../../environment/browser/environmentService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -90,9 +89,7 @@ suite('ConfigurationEditing', () => { 'default': 'isSet', policy: { name: 'configurationEditing.service.policySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } } } diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index ab268311fd1..ebbda208696 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -52,7 +52,6 @@ import { IUserDataProfileService } from '../../../userDataProfile/common/userDat import { TasksSchemaProperties } from '../../../../contrib/tasks/common/tasks.js'; import { RemoteSocketFactoryService } from '../../../../../platform/remote/common/remoteSocketFactoryService.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier { return { @@ -782,9 +781,7 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': 'isSet', policy: { name: 'configurationService.folder.policySetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, 'configurationService.folder.policyObjectSetting': { @@ -792,9 +789,7 @@ suite('WorkspaceConfigurationService - Folder', () => { 'default': {}, policy: { name: 'configurationService.folder.policyObjectSetting', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, } diff --git a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts index 795a4c567c8..1e7621472f5 100644 --- a/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/accountPolicyService.test.ts @@ -12,7 +12,6 @@ import { Registry } from '../../../../../platform/registry/common/platform.js'; import { Extensions, IConfigurationNode, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js'; import { DefaultConfiguration, PolicyConfiguration } from '../../../../../platform/configuration/common/configurations.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -39,9 +38,7 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -49,9 +46,7 @@ suite('AccountPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -60,9 +55,7 @@ suite('AccountPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -71,9 +64,7 @@ suite('AccountPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts index cee9adcd499..4484b9d01b2 100644 --- a/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts +++ b/src/vs/workbench/services/policies/test/common/multiplexPolicyService.test.ts @@ -19,7 +19,6 @@ import { InMemoryFileSystemProvider } from '../../../../../platform/files/common import { FileService } from '../../../../../platform/files/common/fileService.js'; import { VSBuffer } from '../../../../../base/common/buffer.js'; import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js'; -import { PolicyCategory } from '../../../../../base/common/policy.js'; const BASE_DEFAULT_ACCOUNT: IDefaultAccount = { enterprise: false, @@ -48,9 +47,7 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueA', policy: { name: 'PolicySettingA', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } } } }, 'setting.B': { @@ -58,9 +55,7 @@ suite('MultiplexPolicyService', () => { 'default': 'defaultValueB', policy: { name: 'PolicySettingB', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined, } }, @@ -69,9 +64,7 @@ suite('MultiplexPolicyService', () => { 'default': ['defaultValueC1', 'defaultValueC2'], policy: { name: 'PolicySettingC', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined, } }, @@ -80,9 +73,7 @@ suite('MultiplexPolicyService', () => { 'default': true, policy: { name: 'PolicySettingD', - category: PolicyCategory.Extensions, minimumVersion: '1.0.0', - localization: { description: { key: '', value: '' } }, value: account => account.chat_preview_features_enabled === false ? false : undefined, } }, diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7a17b276d15..c1d9dc494ce 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -182,9 +182,6 @@ import './contrib/emergencyAlert/electron-browser/emergencyAlert.contribution.js // MCP import './contrib/mcp/electron-browser/mcp.contribution.js'; -// Policy Export -import './contrib/policyExport/electron-browser/policyExport.contribution.js'; - //#endregion