blob: 1b15e48ecbd072cbcb4b1da7145e16e5efae88af [file] [log] [blame]
// Copyright 2022 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.import { exec } from 'child_process';
import DOM from './helpers/DOMFuncs';
import { FileIO } from './helpers/FileIO';
import { loadMIgrationList } from './helpers/migrationList';
import { processQueriedEntries, TEvalExistingEntry } from './helpers/processXML';
import { repoPath } from './helpers/rootPath';
import { groupReplace } from './helpers/textFuncs';
async function init() {
const migrationMap = await loadMIgrationList();
const basePath = `${repoPath}/../tm-qpr-dev/frameworks/base/core/res/res/values/`;
await processQueriedEntries(migrationMap, {
containerQuery: 'declare-styleable[name="Theme"]',
hidable: true,
path: `${basePath}attrs.xml`,
step: 0,
tagName: 'attr',
evalExistingEntry: (_attrValue, migItem, qItem) => {
const { hidden, textContent: currentComment } = DOM.getElementComment(qItem);
if (hidden) migItem.isHidden = hidden;
const { newComment } = migItem;
return [
hidden ? 'update' : 'duplicate',
{
attrs: { name: migItem.replaceToken },
...(newComment
? { comment: `${newComment} @hide ` }
: currentComment
? { comment: hidden ? currentComment : `${currentComment} @hide ` }
: {}),
},
];
},
evalMissingEntry: (_originalToken, { replaceToken, newComment }) => {
return {
tagName: 'attr',
attrs: {
name: replaceToken,
format: 'color',
},
comment: `${newComment} @hide `,
};
},
});
// only update all existing entries
await processQueriedEntries(migrationMap, {
tagName: 'item',
path: `${basePath}themes_device_defaults.xml`,
containerQuery: 'resources',
step: 2,
evalExistingEntry: (_attrValue, { isHidden, replaceToken, step }, _qItem) => {
if (step[0] != 'ignore')
return [
isHidden ? 'update' : 'duplicate',
{
attrs: { name: replaceToken },
},
];
},
});
// add missing entries on specific container
await processQueriedEntries(migrationMap, {
tagName: 'item',
path: `${basePath}themes_device_defaults.xml`,
containerQuery: 'resources style[parent="Theme.Material"]',
step: 3,
evalMissingEntry: (originalToken, { newDefaultValue, replaceToken }) => {
return {
tagName: 'item',
content: newDefaultValue,
attrs: {
name: replaceToken,
},
};
},
});
const evalExistingEntry: TEvalExistingEntry = (_attrValue, { replaceToken, step }, _qItem) => {
if (step[0] == 'update')
return [
'update',
{
attrs: { name: replaceToken },
},
];
};
await processQueriedEntries(migrationMap, {
tagName: 'item',
containerQuery: 'resources',
path: `${basePath}../values-night/themes_device_defaults.xml`,
step: 4,
evalExistingEntry,
});
await processQueriedEntries(migrationMap, {
tagName: 'java-symbol',
path: `${basePath}symbols.xml`,
containerQuery: 'resources',
step: 5,
evalExistingEntry,
});
// update attributes on tracked XML files
{
const searchAttrs = [
'android:color',
'android:indeterminateTint',
'app:tint',
'app:backgroundTint',
'android:background',
'android:tint',
'android:drawableTint',
'android:textColor',
'android:fillColor',
'android:startColor',
'android:endColor',
'name',
'ns1:color',
];
const filtered = new Map(
[...migrationMap]
.filter(([_originalToken, { step }]) => step[0] == 'update')
.map(([originalToken, { replaceToken }]) => [originalToken, replaceToken])
);
const query =
searchAttrs.map((str) => `*[${str}]`).join(',') +
[...filtered.keys()].map((originalToken) => `item[name*="${originalToken}"]`).join(',');
const trackedFiles = await FileIO.loadFileList(
`${__dirname}/resources/whitelist/xmls1.json`
);
const promises = trackedFiles.map(async (locaFilePath) => {
const filePath = `${repoPath}/${locaFilePath}`;
const doc = await FileIO.loadXML(filePath);
const docUpdated = DOM.replaceStringInAttributeValueOnQueried(
doc.documentElement,
query,
searchAttrs,
filtered
);
if (docUpdated) {
await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
} else {
console.warn(`Failed to update tracked file: '${locaFilePath}'`);
}
});
await Promise.all(promises);
}
// updates tag content on tracked files
{
const searchPrefixes = ['?android:attr/', '?androidprv:attr/'];
const filtered = searchPrefixes
.reduce<Array<[string, string]>>((acc, prefix) => {
return [
...acc,
...[...migrationMap.entries()]
.filter(([_originalToken, { step }]) => step[0] == 'update')
.map(
([originalToken, { replaceToken }]) =>
[`${prefix}${originalToken}`, `${prefix}${replaceToken}`] as [
string,
string
]
),
];
}, [])
.sort((a, b) => b[0].length - a[0].length);
const trackedFiles = await FileIO.loadFileList(
`${__dirname}/resources/whitelist/xmls2.json`
);
const promises = trackedFiles.map(async (locaFilePath) => {
const filePath = `${repoPath}/${locaFilePath}`;
const doc = await FileIO.loadXML(filePath);
const docUpdated = DOM.replaceContentTextOnQueried(
doc.documentElement,
'item, color',
filtered
);
if (docUpdated) {
await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
} else {
console.warn(`Failed to update tracked file: '${locaFilePath}'`);
}
});
await Promise.all(promises);
}
// replace imports on Java / Kotlin
{
const replaceMap = new Map(
[...migrationMap.entries()]
.filter(([_originalToken, { step }]) => step[0] == 'update')
.map(
([originalToken, { replaceToken }]) =>
[originalToken, replaceToken] as [string, string]
)
.sort((a, b) => b[0].length - a[0].length)
);
const trackedFiles = await FileIO.loadFileList(
`${__dirname}/resources/whitelist/java.json`
);
const promises = trackedFiles.map(async (locaFilePath) => {
const filePath = `${repoPath}/${locaFilePath}`;
const fileContent = await FileIO.loadFileAsText(filePath);
const str = groupReplace(fileContent, replaceMap, 'R.attr.(#group#)(?![a-zA-Z])');
await FileIO.saveFile(str, filePath);
});
await Promise.all(promises);
}
}
init();